P706)必须用一个检查感兴趣的while循环包围wait()。其本质就是要检查所感兴趣的特定条件,并在条件不满足的情况下返回到wait()中。
P707)notify()与notifyAll()
使用notify()时,在众多等待同一个锁的任务中只有一个会被唤醒,因此如果你希望使用notify(),就必须保证被唤醒的是恰当的任务。另外,为了使用notify(),所有任务必须等待相同的条件,因此如果你有多个任务在等待不同的条件,那么你就不知道是否会唤醒了恰当的任务。如果使用notify(),当条件发生变化时,必须只有一个任务能够从中受益。最后,这些限制对所有可能存在的子类都必须总是起作用的。如果这些规则中有任何一条不满足,那么你就必须使用notifyAll()而不是notify()。
notifyAll()将唤醒“所有正在等待的任务”。这是否意味着程序中任何地方,任何处于wait()状态中的任务都将被任何对notifyAll()的调用唤醒呢?事实上,当notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。
P707)Timer
1 Timer timer = new Timer();2 timer.scheduleAtFixedRate(new TimerTask() {3 @Override4 public void run() {5 //...6 }7 }, 400, 400);8 timer.cancel();
P711)使用显式的Lock和Condition对象
使用互斥并允许任务挂起的基本类是Condition,你可以通过在Condition上调用await()来挂起一个任务。当外部条件发生变化,意味着某个任务应该继续执行时,你可以通过调用signal()来通知这个任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务(与使用notifyAll()相比,signalAll()是更安全的方式)。
P713)生产者-消费者与队列
LinkedBlockingQueue——无界队列
ArrayBlockingQueue——确定的、固定的尺寸
SynchronousQueue——尺寸固定为1
P717)任务间使用管道进行输入/输出
提供线程功能的类库以“管道”的形式对线程间的输入/输出提供了支持。它们在Java输入/输出类库中的对应物就是PipedWriter类(允许任务向管道写)和PipedReader类(允许不同任务从同一个管道读取)。PipedReader是可中断的。
P721)死锁发生的条件
- 互斥条件。任务使用的资源至少有一个是不能共享的。这里,一根Chopstick一次就只能被一个Philosopher使用。
- 至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。
- 资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌,他们不会从其他Philosopher那里抢Chopstick。
- 必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。
因为要发生死锁的话,所有这些条件必须全部满足;所以要防止死锁的话,只需破坏其中一个即可。
P724)Random是线程安全的,但是其并发性能很差。
P754)基本上,如果涉及多个Atomic对象,你就有可能会被强制要求放弃这种用法,转而使用更加常规的互斥(JDK文档特别声明:当对一个对象的临界更新被限制为只涉及单个变量时,只有使用Atomic对象这种方式才能工作)。
P754)免锁容器
Collections类提供了各种static的同步修饰方法(Collections.synchronizedList(List)),从而同步不同的容器。尽管这是一种改进,因为它使你可以选择在你的容器中是否要使用同步,但是这种开销仍旧是基于synchronized加锁机制的。
P755)在CopyOnWriteArrayList中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全的执行。当修改完成时,一个性的操作将把新的数组换入,使得新的读取操作可以看到这个新的修改。CopyOnWriteArrayList的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException,因此你不必编写特殊的代码去防范这种异常。
CopyOnWriteHashMap和ConcurrentLinkedQueue使用了类似的技术,允许并发的读取和写入,但是容器中只有部分内容而不是整个容器可以被复制和修改。然而,任何修改在完成之前,读取者仍旧不能看到它们。ConcurrentHashMap不会抛出ConcurrentModificationException异常。
P761)ReadWriteLock对数据结构相对不频繁的写入,但是有多个任务要经常读取这个数据结构的这类情况进行了优化。ReadWriteLock使得你可以同时有多个读取者,只要它们都不试图写入即可。如果写锁已经被其他任务持有,那么任何读取者都不能访问,直至这个写锁被释放为止。
ReadWriteLock是否能够提高程序的性能是完全不可确定的,它取决于诸如数据被读取的频率与被修改的频率相比较的结果,读取和写入操作的时间(锁将更复杂,因此短操作并不能带来好处),有多少线程竞争以及是否在多处理机器上运行等因素。最终,唯一可以了解ReadWriteLock是否能够给你的程序带来的好处的方式就是用试验来证明。
P763)如果你在JDK文档中查看ReentrantReadWriteLock,就会发现还有大量的其他方法可用,涉及“公平性”和“政策性决策”等问题。这是一个相当复杂的工具,只有当你在搜索可以提高性能的方法时,才应该想到用它。你的程序的第一个草案应该使用更直观的同步,并且只有在必需时再引入ReadWriteLock。