第11章 并发
78,同步访问共享的可变数据
为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的。
这归因于 Java 语言规范中的内存模型,它规定了一个线程所做的变化以及何时如何变成对其他线程是可见的。
千万不要使用 Thread.stop 方法,因为它本质是不安全的 — 使用它会导致数据遭到破坏。
未能同步共享可变的变量会造成程序的活性失败和安全性失败。
如果只需要线程之间相互通信,而不需要互斥,volatile 修饰符就是一种可接受的同步形式,但是要正确使用它需要一些技巧。
79,避免过度同步
为了避免活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制。
为了避免死锁和数据破坏,千万不要从同步区域调用外来方法。更通俗的讲,要尽量将同步区域内部的工作量限制到最少。
80,executor,task和stream优先于线程
ExecutorService exec = Executors.newSingleThreadExecutor();
//提交一个runnable方法
exec.execute(runnable);
//终止
exec.shutdown();
在Executor Framework 中,工作单元和执行机制是分开的。
现在关键的抽象是工作单元,称作任务(task)。
并发的 stream 是在 fork join 池上编写的 。
81,并发工具优于wait和notify
JUC 中更高级的功能包含三类:
- Executor Framework
- 并发集合(Concurrent Collection)
- 同步器(Synchronizer)
并发集合为标准的接口,提供了高性能的并发实现,为了提供稿并发性,这些实现在内部自己管理同步。因此,并发集合不可能排除并发活动;将它锁定没什么作用,只会是程序速度减慢。
应该优先使用 ConcurrentHashMap,而不是使用 Collection.synchronizedMap。
同步器是使线程能够等待另一个线程的对象,允许它们协调运作。最常用的同步器是 CountDownLatch 和Semaphore,较不常用的是 CyclicBarrier 和 Exchanger,功能最强大的同步器是 Phaser。
对于间歇性定时,始终应该优先使用 System.nanoTime,而不是 System.CurrentTimeMills。
应该使用 wait 循环模式来调用 wait 方法;永远不要再循环之外调用 wait 方法。
一般情况下,永远优先使用 notifyAll 方法。而不是 notify 方法,如果使用 notify 方法,请一定要小心,以确保程序的活性。
82,线性安全性的文档化
一个类为了可被多个线程安全的使用,必须在文档中清楚地说明它所支持地线程安全级别。
线程安全级别:
- 不可变的
- 无条件的线程安全。
- 有条件的线程安全。
- 非线程安全。
- 线程对立的:这种类不能安全的被多个线程并发使用,即使所有的方法调用都被外部同步包围。
lock 域始终被声明为 final 的。
私有锁对象模式只能用在无条件的线程安全类上。
83,慎用延迟初始化
延迟初始化:延迟到需要域的值时才能将它初始化。
在大多数情况下,正常的初始化要优先于延迟初始化。
如果利用延迟初始化来破坏初始化的平衡,就要使用同步访问方法。
如果出于性能的考虑而需要对静态域使用延迟初始化,就使用 lazy initialization holder class 模式。
对于实例域,使用双重检查模式。
对于静态域,则使用 lazy initialization holder class 模式。
对于可接受重复初始化的实例域,也可以考虑单重检查模式。
84,不要依赖于线程调度器
线程调度器:线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间,
线程调度并不受到 Java 虚拟机控制。
链接:https://segmentfault.com/a/1190000020669649?utm_source=tag-newest
任何依赖线程调度器来达到正确性或者性能要求的程序,很有可能是不可移植的。
不要企图通过调用 Thread.yield 来修正程序。
Thread.yield 没有可测试的语义。
线程优先级是 Java 平台上最不可能移植的特征了。