Effective Java阅读笔记(9)
Effective Java第10章 并发
66.同步访问共享的可变数据
当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。
67.避免过度同步
为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法,并要尽量限制同步区域内部的工作量。
68.executor和task优先于线程
现在应当优先使用executor和task而不是Thread,因为在以前Thread既是工作单元又是执行单元,但是现在二者分离,executor是执行单元,Runnable和Callable是工作单元。
使用ScheduledThreadPool代替Timer作为多线程的定时器。
对于ThreadPool:
- 使用
Executor.newCachedThreadPool来应对一个轻负载的服务器。 - 使用
Executor.newFixedThreadPool来应对一个重负载的服务器。 - 为了最大限度控制它,直接使用
ThreadPoolExecutor。
69.并发工具优先于wait和notify
考虑使用java.util.concurrent而非wait和notify。如果你在维护使用wait和notify的代码,务必确保始终是利用标准的模式从while循环内部调用wait。一般情况下,你应该优先使用notifyAll,而不是使用notify。如果使用notify,请一定要小心,以确保程序的活性。
70.线程安全性的文档化
线程安全性的几种级别:
不可变的
immutable,这个类的实例是不变的,所以不需要外部的同步。例如:String、Long和BigInteger。无条件的线程安全
unconditionally thread-safe,这个类的实例是可变的,但是这个类有着足够的内部同步,所以它的实例可以被并发使用,无需任何外部同步。例如:Random和ConcurrentHashMap。有条件的线程安全
conditionally thread-safe,除了有些方法为进行安全的并发而使用需要外部同步之外,这种线程安全级别与无条件的线程安全相同。例如:Collections.synchronized包装返回的集合,它们的迭代器iterator要求外部同步。非线程安全
not thread-safe,这个实例是可变的。为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法调用(或者调用序列)。例如:ArrayList和HashMap。线程对立的
thread-hostie,这个类不能安全地被多个线程并发使用,即使所有的方法调用都被外部同步包围。
如果编写无条件的线程安全类,建议使用私有锁,隐藏内部实现,私有锁尤其适合于专门为继承而设计的类。如果编写有条件的线程安全类,一定要在API文档中注明哪个方法调用序列需要外部同步,以及执行这些序列需要哪把锁。
71.慎用延迟初始化
除非绝对必要,否则就不要这么做。如果域只在类的实例部分被访问,并且初始化这个域的开销很高,可能就值得进行延迟初始化。在大多数情况下,正常的初始化要优先于延迟初始化。
四种延迟初始化的方式:
1.最普通的:
private FieldType field;
synchronized Field getField(){
if(field==null){
field=computeFieldValue();
}
return field;
}
2.对静态域的出于性能考虑的初始化方式:
private static class FieldHolder{
static final FieldType field=computeFieldValue();
}
static FieldType getField(){ return FieldHolder.field; }
3.双重检查模式(出于性能考虑,避免初始化之后的锁定访问的开销):
private volatile FieldType field;
Field getField(){
FieldType result=field;
if(result==null){
synchronized(this){
result=field;
if(result==null){
field=result=computeFieldValue();
}
}
}
return result;
}
4.单重检查模式(双重检查模式的变式,可以接受重复初始化):
private volatile FieldType field;
private FieldType getField(){
FieldType result=field;
if(result==null){
field=result=computeFieldValue();
}
return result;
}
72.不要依赖于线程调度器
线程只做有意义的工作,不应该忙等。
不要依赖Thread.yield改变优先级,因为它在不同环境下效果不同,而且实现也不同。对于大多数程序员来说,Thread.yield的唯一用途是在测试期间人为地增加程序的并发性。
不要使用调整线程优先级的方法,它不具有可移植性。
线程优先级可以提高一个已经能够正常工作的程序的服务质量,但永远不应该用来“修正”一个本来不能工作的程序。
要编写健壮的、相应良好的、可移植的多线程应用程序,最好的办法是确保可运行线程的平均数量不明显多于处理器的数量,这使得线程调度器没有更多的选择:它只需要运行这些可运行的线程,直到它们不再可运行。在等待的线程并不是可运行的。
73.避免使用线程组
ThreadGroup API已经过时。