第1讲介绍了产生并发编程Bug的原因:缓存导致的可见性问题、线程切换带来的原子性、编译优化带来的有序性问题。
为了均衡CPU和内存的速度差异,CPU引入了缓存的概念。在多核时代,每个CPU都有自己的缓存,多个线程操纵的CPU缓存是不同的,因此一个线程对共享变量的修改对另一个线程可能不是立即可见的。通过让每个进程每次只执行一个时间片的时间操作系统做到了分时复用。进程间做任务切换需要切换内存映射地址,而线程间切换避免了切换内存映射地址的开销,因此现代操作系统都在线程间进行任务调度。高级语言的一条语句往往需要多个时间片,而这些时间片往往不是连续的。
为了优化性能,编译器会更改程序中语句的执行顺序。
第2讲介绍了Java内存模型,它规范了JVM如何提供按需禁用缓存和编译优化的方法。这些方法主要指的是Happens-Before原则,该原则的含义是前面的操作对后续操作是可见的。
程序的顺序性原则:在一个线程中,前面的操作Happens-Before后续操作。volatile原则:对一个volatile变量的写操作Happens-Before于后续对该变量的读操作。传递性原则:A操作Happens-Before于B操作,B操作Happens-Before于C操作,则A操作Happens-Before于C操作。管程中锁的规则:对一个锁的解锁Happens-Before于后续对这个锁的加锁。线程start原则:子线程启动后,子线程可以看到主线程启动自己前的操作。线程join原则:子线程结束后,主线程可以看到子线程的操作。线程interrupt原则:对线程interrupt方法的调用Happens-Before于被中断线程检测到中断事件的发生。第3讲介绍了使用互斥锁解决原子性问题。
对于单核CPU来说,禁止CPU中断可以解决原子性问题:同一时刻只有一个线程执行,禁止CPU中断也就杜绝了线程切换。如果能保证任一时刻最多只有一个线程访问共享变量,则在多核CPU上也保证了原子性。受保护资源和锁之间的关联关系是N:1的,也就是说可以用一把锁保护多个资源,但不能用多把锁保护一个资源。第4讲介绍了如何保护多个资源。
如果各资源间没有关联关系,就可以用细粒度锁,也就是每个资源一把锁。如果各资源间存在关联关系,就需要用粗粒度锁,也就是锁要覆盖所有资源。第5讲介绍了如何预防死锁。
死锁有四个条件:互斥;占有且等待;不可抢占;循环等待。一次性申请所有资源可以破环占有且等待条件。 class Allocator { private List<Object> als = new ArrayList<>(); // 一次性申请所有资源 synchronized boolean apply( Object from, Object to){ if(als.contains(from) || als.contains(to)){ return false; } else { als.add(from); als.add(to); } return true; } // 归还资源 synchronized void free( Object from, Object to){ als.remove(from); als.remove(to); } } class Account { // actr应该为单例 private Allocator actr; private int balance; // 转账 void transfer(Account target, int amt){ // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, target)) ; try{ // 锁定转出账户 synchronized(this){ // 锁定转入账户 synchronized(target){ if (this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } finally { actr.free(this, target) } } }占有资源的线程申请其它资源失败时放弃自己的资源可以破环不可抢占条件。synchronized是做不到这点的,因为如果申请不到所需的资源线程就阻塞了。通过按序申请资源可以破环循环等待条件。 class Account { private int id; private int balance; // 转账 void transfer(Account target, int amt){ Account left = this ① Account right = target; ② if (this.id > target.id) { ③ left = target; ④ right = this; ⑤ } ⑥ // 锁定序号小的账户 synchronized(left){ // 锁定序号大的账户 synchronized(right){ if (this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } }