EffectiveJava(笔记九) 并发

来源:互联网 发布:三维设计教学软件 编辑:程序博客网 时间:2024/06/06 07:00

66. 同步访问共享的可变数据

关键字synchronized可以保证在同一时刻, 只有一个线程可以执行某一个方法, 或者某一个代码块, 为了在线程之间进行可靠的通信, 也为了互斥访问, 同步是必要的, 多个线程共享可变数据的时候, 每个读或者写数据的线程都必须执行同步, 未能同步共享可变数据会造成程序的活性失败和安全性失败

67. 避免过渡同步

第66条告诫我们缺少同步的危险性, 本条目则关注相反的问题, 过渡同步可能会导致性能降低, 死锁甚至不确定的行为
虽然自从Java平台早期以来, 同步的成本已经下降了, 但更重要的是, 永远不要过度同步, 在这个多核时代, 过度同步的实际成本并不是指获取锁所花费的CPU时间, 而是指失去了并行的机会, 以及因为需要确保每个核都有一个一致地内存视图而导致的延迟, 过度同步的另一项潜在开销在于, 它会限制VM优化代码执行的能力
总之, 为了避免死锁和数据破坏, 千万不要从同步区域内部调用外来方法, 简而言之, 就是要尽量限制同步区域内部的工作量, 当在设计一个可变类的时候, 要考虑一下它们是否应该自己完成同步操作, 在这个多核的时代, 这比永远不要过度同步来得更重要

68. executor和task优先于线程

executor提供了丰富的线程池静态工厂, 以满足各种任务需求
ExecutorFramework也有一个可以代替java.util.Timer的东西, 即ScheduledThreadPoolExecutro, 虽然timer使用起来更加容易, 但是被调度的线程池executro更加灵活, timer只用一个线程来执行任务, 这在面对长期运行的任务时, 会影响到定时的准确性, 如果timer唯一的线程抛出未被捕获的异常, timer就会停止执行, 被调度的线程池executor支持多个线程, 并且优雅地从抛出未受检异常的任务中恢复

69. 并发工具优先于wait和notify

既然正确地使用wait和notify比较困难, 就应该用更高级的并发工具来代替, java.util.concurrent中更高级的工具分成三类: Executor Framework, 并发集合以及同步器
并发集合为标准的集合接口(List Queue Map)提供了高性能的并发实现, 为了提供高并发性, 这些实现在内部自己管理同步, 因此, 并发集合中不可能排除并发活动, 将它同步锁定没有什么作用, 只会使程序的速度更慢, 并发集合有ConcurrentMap

70. 线程安全性的文档化

线程安全性级别:
* 不可变的: 这个类的实例是不变得, 所以, 不需要外部的同步, 如: String Long BigInteger
* 无条件的线程安全: 这个类的实例是可变的, 但是这个类有着足够的内部同步, 所以, 它的实例可以被并发使用, 无需任何外部同步, 如果: Random和ConcurrentHashMap
* 有条件的线程安全: 除了有些方法为进行安全的并发使用而需要外部同步之外, 这种线程安全级别与无条件的线程安全相同, 如: Collections.synchronized包装返回的集合, 它们的迭代器要求外部同步
* 非线程安全: 这个类的实例是可变, 为了并发地使用它们, 客户必须利用自己选择的外部同步包围每个方法调用, 如: ArrayList和HashMap
* 线程对立的: 这个类不能安全地被多个线程病房使用, 即使所有的方法调用都被外部同步包围, 线程对立的根源通常在于, 没有同步地修改静态数据, 在Java平台类库中, 线程对立的类或者方法非常少, 有也被废除了

71. 慎用延迟初始化

延迟初始化是延迟到需要域的值时才将它初始化的这种行为, 如果永远不需要这个值, 这个域就永远不会被初始化, 这种方法既适用于静态域, 也适用于实例域, 延迟初始化就像一把双刃剑, 它降低了初始化类或者创建实例的开销, 却增加了方法被延迟初始化的域的开销, 根据延迟初始化的域最终需要初始化的比例以及初始化这些域要多少开销, 以及每个域多久会被访问一次, 延迟初始化实际降低了性能
延迟初始化有它的好处, 如果域只在类的实例部分被访问, 并且初始化这个域的开销很高, 可能就值得进行延迟初始化

private volatile FieldType field;FieldType getField() {    FieldType result = field;    if (result == null) {        synchronized(this) {            result = field;            if(result == null)                 field = result = computeFieldValue();        }    }    return result;}

这段代码可能看起来似乎有些费解, 尤其对于需要用到局部变量result可能有点不解, 这个变量的作用是确保field只在已经被初始化的情况下读取一次, 虽然这不是严格需要, 但是可以提升性能, 这个方法要比没有局部变量的方法快25%

原创粉丝点击