《Effective Java》第10章 并发

来源:互联网 发布:万博宣伟 知乎 编辑:程序博客网 时间:2024/06/01 09:37

本章给出一些建议帮助程序员编写出清晰,正确,文档组织良好的并发程序。

1. 同步访问共享的可变数据 【Item 66】
1) 正确使用同步可以保证没有任何方法会看到对象处于不一致的状态中
2) Java程序语言保证读写单个变量是原子的,除非这个变量的类型是long或者double
3) 为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的,这归因域Java语言规中的内存模型,其规定了一个线程所做的变化何时以及如何变成对其它线程可见
4) Java类库中提供了Thread.stop方法,但是不要使用,因为该调用会导致数据遭到破坏
5) 在做同步时一定要精准,如果读写操作都没有被同步,那么同步就不会起作用
6) 虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚写入的值,因为JVM在写入该类变量时会将之前的cache都刷掉
7) 增量操作符(++)不是原子的
8) 尽量不共享可变的数据,将可变的数据限制在单个线程中
9)安全发布对象引用有多种办法:
a. 可以将它保存在静态域中,作为类初始化的一部分
b. 可以将它保存在volatile域,final域
c. 可以将它保存在通过正常锁定访问的域中
d. 可以将它放到并发的集合中
10) 当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步
11) 如果只需要线程之间的交互通信,而不需要互斥,volatile修饰符就是一种可以接受的同步形式
2. 避免过度同步 【Item 67】
1) 为了避免活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制
2) 通常你应该在同步区域内做尽量少的工作
3) 过度同步的缺点:
a. 多核的时代,过度同步的实际成本并不是指获取锁所花费的CPU时间,而是指失去了并行的机会以及需要确保每个核都有一个一致的内存视图而导致的延迟
b. 过度同步会限制VM优化代码执行的能力
4) 如果一个可变的类要并发使用,应该使这个类变成线程安全的,通过内部的同步,你还可以获得明显比从外部锁定整个对象更高的并发性
5) 当你不确定的时候,就不要同步你的类,而是应该建立文档,注明它不是线程安全的
6) 如果方法修改了静态域,那么你也必须同步对这个域的访问
7) 为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法
3. executor和task优先于线程 【Item 68】
1)这些用于多线程的类库能够更加完善的执行多线程任务,从性能,易用性,降低出错率上都有好处
4. 并发工具优先于wait和notify 【Item 69】
1)正确使用wait和notify有一定的难度,那么建议使用同步类库java.util.concurrent
2) 同步类库中有更高级的工具:
a. Executor Framework
b. 并发集合(Concurrent Collection)
c. 同步器(Synchronizer)
3) 同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作
4) 对于间歇式的定时,使用应该优先使用System.nanoTime,而不是使用System,currentTimeMills,前者更加准确也更加精准,它不受系统的实时时钟的调整所影响
5) wait方法被用来使线程等待某个条件,它必须在同步区域内部被调用,这个同步区域将对象锁定在了调用wait方法的对象上
6) 始终应该使用wait循环模式来调用wait方法,永远不要在循环之外调用wait方法
7) 从优化的角度来看,如果处于等待状态的所有线程都在等待同一个条件,而每次只有一个线程可以从这个条件中被唤醒,那么你就应该选择notify,而不是notifyAll
8) 没有理由在新代码中使用wait和notify
9) 如果你在维护使用wait和notify的代码,务必确保始终是利用标准模式从while循环内部调用wait
10) 一般情况下,你应该优先调用notifyAll,而不是notify,如果使用notify,请一定要小心,以确保程序的活性
5. 线程安全性的文档化 【Item 70】
1) 如果你没有在一个类的文档中描述其行为的并发性情况,使用这个类的程序员将不得不做出某些假设,假设错误那么可能存在缺少同步问题或者同步过度问题
2) 一个类为了可以被多个线程安全地使用,必须在文档中清楚地说明它所支持的线程安全级别
3) 线程安全级别:
a. 不可变(immutable),该类的实例不可变,不需要外部同步,例如String,Long和BigInteger
b. 无条件线程安全(unconditionally thread-safe),该类的实例可变,但是类有足够的内部同步,其可以被并发使用,无需任何外部同步,例如Ramdom和ConcurrentHashMap
c. 有条件线程安全(conditionally thread-safe),除了有些方法为进行安全的并发使用而需要外部同步外,此级别同无条件的线程安全相同,例如包括Collections.synchronized包装返回的集合,它们的Iterator需要外部同步
d. 非线程安全(not thread-safe),该类的实例可变,为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法的调用(或者调用序列),例如通用的集合实现ArrayList和HashMap等
e. 线程对立(thread-hostile),这个类不能安全的被多个线程并发使用,即使所有的方法调用都被外部同步包围,线程对立的根源通常在于没有同步地修改静态数据
4) 没有必要说明枚举类型的不可变性
5) 除非从返回类型来看已经很明显,否者静态工厂必须在文档中说明被返回对象的线程安全性
6) 注意lock域被声明为final的,这样可以防止不小心改变它的内容,而导致不同步访问包含对象的悲惨后果
7) 私有锁对象模式只能用在无条件的线程安全类上
8) 私有锁对象模式特别适用于那些专门为继承儿设计的类
9) 有条件的线程安全类必须指明哪些方法需要外部同步控制保证并发的安全性
6. 慎用延迟初始化 【Item 71】
1) 如果域只在类的实例部分被访问,并且初始化这个域的开销很高,可能值得延迟初始化
2) 如果两个或者多个线程共享一个延迟初始化的域,采用某种形式的同步很重要,否则可能造成严重的bug
3) 大多数情况下,正常的初始化要优先于延迟初始化
7. 不要依赖于线程调度器 【Item 72】
1) 任何依赖于线程调度器来达到正确性或者性能要求的程序,很可能都是不可移植的
2) 如果线程没有在做有意义的工作,就不应该运行
3) 由于Thread.yield的随JVM不同差异大,因此不要依赖它来“修正”程序,该方法的唯一作用是在测试期间人为地增加程序的并发性
4) 线程优先级是Java平台上最不可移植的特征了
5) 不要让应用程序的正确性依赖域线程调度器,否则应用程序既不健壮,也不具有可移植性,作为推论,不要依赖于Thread.yield或者线程优先级
8. 避免使用线程组 【Item 73】
线程组已经过时,使用线程池代替

0 0
原创粉丝点击