Effictive Java 并发摘录
1、同步访问共享的可变数据
- java语言规范保证读或者写一个变量是原子的,除非这个变量的类型是long类型或者double类型;
- 没有同步就不能保证一个线程写一个类的变量之后,另一个同时读该变量的线程能够何时“看到”;可以通过synchronized对变量的读、写方法同时进行同步来解决这一问题,或者将该变量声明为volatile的(虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值。),不过volatile声明在处理变量自增等类似情况的时候就达不到想要的效果,解决方法是:用synchronized进行线程互斥访问了该方法(如果变量是long可以换改成AtomicLong类型,其getAndIncrement()方法就已经实现同步了);
- 简而言之,当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。如果没有同步就不能保证一个线程所做的修改可以被另一个线程获知。
2、避免过度同步
- 在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客户端以函数对象的形式提供的方法。从包含该同步区域的类的角度来看,这样的方法是外来的。这个类不知道该方法会做什么事情,也无法控制它。根据外来方法的作用,从同步区域中调用它会导致异常、死锁或者数据损坏。
- Java程序设计语言中的锁是可重入的,当一个类的所有方法都是用类引用作为锁时,一个线程如果获取到了一个方法的访问权(拿到锁),它就可以在这个方法内部调用该类的其他方法(或者说,该线程想要再次获得该锁时会成功)而不会阻塞。从本质上讲,这个锁没有尽到它的职责。可再入的锁简化了多线程的面向对象程序的构造,但是它们可能会将活性失败变成安全性失败。
3、办法工具优先于wait和notify
- 直接使用wait和notify就像用“并发汇编语言”进行编程一样,而java.util.concurrent则提供了更高级的语言。没理由在新代码中使用wait和notify,即使有,也是极少的。如果你在维护使用wait和notify代码,务必确保始终是利用标准的模式从while循环内部调用wait。一般情况下,你应该优先使用notifyAll,而不是使用notify。如果使用notify,请一定要小心,以确保程序的活性。
4、慎用延迟初始化
- 大多数的域应该正常地进行初始化,而不是延迟初始化。如果为了达到性能目标,或者为了破坏有害的初始化循环,而必须延迟初始化一个域,就可以使用相应的延迟初始化方法。对于实例域,就使用双重检查模式;对于静态域,则使用lazy initialization holder class idiom。对于可以接受重复初始化的实例域,也可以考虑使用单重检查模式。
双重初始化:
private volatile FieldType field;
FieldType getField(){
FieldType result = field;
if(result == null){
synchronized(this){
result = field;
if(result == null)
field = result = computerFieldValue();
}
}
return result;
}
注:局部变量result的作用是确保field只在已经被初始化的情况下读取一次。这样可以提升性能。