目录
- 同步访问共享的可变数据
- 避免过度同步
- EXECUTORS, TASKS, STREAMS 优于线程
- 优先使用并发实用程序替代wait和notify
- 线程安全文档化
- 明智谨慎地使用延迟初始化
- 不要依赖线程调度器
并发
同步访问共享的可变数据
- 当多个线程共享可变数据时,每个读取或写入数据的线程都必须执行同步。 在没有同步的情况下,无法保证一个线程的更改对另一个线程可见。 未能同步共享可变数据的代价是活性失败和安全性失败。 这些失败是最难调试的。 它们可以是间歇性的和时间相关的,并且程序行为可能在不同VM之间发生根本的变化。如果只需要线程间通信,而不需要互斥,那么volatile修饰符是一种可接受的同步形式,但是正确使用它可能会比较棘手。
避免过度同步
- 为了避免活性失败和安全性失败 在一个被同步的方法或者代码块中 永远不要放弃对客户端的控制。通常,应该在同步区域内做尽可能少的工作
- 活性失败,快速失败,安全失败可参考关于活性失败、快速失败、安全失败
- happens-before规则可参考深入理解happens-before规则
executor和task优先于线程
- rt
并发工具优先于wait和notify
- 正确地使用wait和notify比较困难 就应该用更高级的并发工具来代替
并发集合中不可能排除并发活动 将它锁定没有什么作用 只会使程序的速度变慢 - 除非不得已 否则应该优先使用ConcurrentHashMap 而不是使用Collections.synchronizedMap或者Hashtable
- 对于间歇式的定时 始终应该优先使用System.nanoTime 而不是使用System.currentTimeMills System.nanoTime更加准确也更加精确 它不受系统的实时时钟的调整所影响
- 始终应该使用wait循环模式来调用wait方法 永远不要在循环之外调用wait方法。wait方法需要释放锁,前提条件是它已经持有锁。所以wait和notify(或者notifyAll)方法都必须被包裹在synchronized语句块中,并且synchronized后锁的对象应该与调用wait方法的对象一样。否则抛出IllegalMonitorStateException。wait是在当前线程持有wait对象锁的情况下,暂时放弃锁,并让出CPU资源,并积极等待其它线程调用同一对象的notify或者notifyAll方法。
- 关于wait和notify的应该可参考wait、notify应用场景(生产者-消费者模式)
线程安全性的文档化
线程安全级别
- 不可变的
- 无条件的线程安全
- 有条件的线程安全
- 非线程安全
- 线程对立的
慎用延迟初始化
- 在大多数情况下 正常的初始化要优先于延迟初始化
- 如果出于性能的考虑而需要对静态域使用延迟初始化 就使用lazy initialization holder class模式
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
- 如果出于性能的考虑而需要对实例域使用延迟初始化 就使用双重检查模式
不要依赖于线程调度器
- 任何依赖线程调度器来保证正确性或性能的程序都可能是不可移植的。
- 线程优先级是Java中最不可移植的功能之一。
- 不要依赖线程调度器来确定程序的正确性。 由此产生的程序既不健壮也不可移植。 作为推论,不要依赖Thread.yield方法或线程优先级。 这些机制仅仅是对调度器的提示。 可以谨慎地使用线程优先级来提高已经工作的程序的服务质量,但是它们永远不应该用于“修复”几乎不起作用的程序。
网友评论