两个基本概念-》
并发:有资源竞争
并行:没有资源竞争
线程上下文切换
使用多线程的目的是为了充分利用CPU,但要认识到,每个CPU同一时刻只能被一个线程使用。
cpu通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换。这种切换是有时间开销的。因此只有在cpu资源浪费的情况下使用多线程能提高效率,否则多个线程抢一个cpu,线程创建和上下文切换的开销比较高。

这里可以使用Lmbench3来测量上下文切换时长。
使用vmstat来测量上下文切换次数。

顺便提一下java自带的一些工具:
jps:虚拟机进程状况工具,可以列出虚拟机进程
jstat:虚拟机统计信息监视工具 显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据
jinfo:java配置信息工具 实时查看和调整虚拟机各项参数
jmap:java内存映像工具 生成堆转储快照
jhat:堆转储快照分析工具(也可以用EMA啊,可视化多方便)
jstack:堆栈跟踪工具 用于生成虚拟机当前时刻的线程快照(可用于分析死锁等问题)

减小上下文切换的四种方式

  • 无锁并发编程:避免使用锁(避免多个同时访问共享数据,可以使用hash算法来分配每个线程处理的数据区域)
  • CAS算法 无需加锁实现多线程安全
  • 使用最少线程 不创建不必要的线程
  • 协程 在单线程内实现多任务的调度并维持任务间切换

死锁问题
产生死锁会导致系统功能不可用
下面是一个死锁的例子

class DeadLockDemo {
    private static String A="A";
    private static String B="B";

    public static void main(String[] args) {
        new DeadLockDemo().deadLock();
    }
    private void deadLock(){
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (A){
                    try {
                        Thread.currentThread().sleep(2050);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B){
                        System.out.println("1");
                    }
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B){
                    synchronized (A){
                        System.out.println("2");
                    }
                }
            }
        });
        t1.start();
        t2.start();
        System.out.println(2);
    }
}

线程1获取了A的锁,然后休眠,此时线程2获取了B锁,A线程恢复运行后尝试获取B锁,但线程2此时等待A锁释放,由此造成了死锁。
出现死锁后,将不能继续服务,只能通过dump线程查看
到底是哪个线程出现了问题。

避免死锁的方法
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁lock.tryLock(timeout)来代替内部锁
对于数据库锁,加锁和解锁需要在一个数据库连接,否则可能产生解锁失败。