基础构建模块
Java平台类库包含了丰富的并发基础构建模块,例如线程安全的容器类以及同步工具类。
同步容器类
同步容器包括Vector、HashTable,Collections.synchronizedXxx等工厂方法创建的同步封装器类。
这些类实现线程同步的方式是:将状态封装起来,并对每个公有方法同步,使得每次只有一个线程能访问容器的状态。
同步容器类的问题
复合操作,如:迭代、跳转、条件运算等,需要额外的客户端加锁来保证线程安全性。
迭代器与ConcurrentModificationException
在设计同步容器类的迭代器时并没有考虑到并发修改的问题,并且表现出的行为是“及时失败”(fail-fast):当发现容器在迭代时被修改,会抛出ConcurrentModificationException。
在迭代过程中持有容器的锁,可以避免ConcurrentModificationException,但长期持有锁可能会造成饥饿或者死锁,降低程序的可伸缩性。
如果不希望加锁,那么替代方式就是克隆容器,在副本上迭代。但是克隆容器时存在着显著的性能开销。
隐藏迭代器
容器的toString()/hashCode()/equals()方法,都可能间接迭代容器,造成ConcurrentModificationException异常。
并发容器
通过并发容器来替代同步容器,可以极大地提高伸缩性并降低风险。
ConcurrentHashMap
实现机制:分段锁
额外的原子Map操作
增加了一些常见的复合操作:
1 | public interface ConcurrentMap<K, V> extends Map<K, V> { |
CopyOnWriteArrayList
“写入时复制”容器每次修改时,都会创建并重新发布一个新的容器副本。
仅当迭代操作远远多于复制操作时,才应该使用“写入时复制”容器,这个准则很好地描述了许多事件通知系统:大多数情况下,注册、注销监听器的操作远少于接收事件并分发通知的操作。
阻塞队列和生产者-消费者模式
在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具:它能够抑制并防止产生过多的工作项,使应用程序在负荷过载的情况下变得更加健壮。
串行线程封闭
对于可变对象,阻塞队列促进了串行线程封闭,将对象的所有权从生产者交付给消费者。线程封闭对象只能由单个线程拥有,独占访问权。
双端队列与工作密取
BlockingDeque
应用场景看不懂
阻塞方法与中断方法
同步工具类
闭锁
闭锁可以用来确保某些活动直到其他活动都完成后才继续执行。
CountDownLatch#await()/countDown()
FutureTask
FutureTask实现了Future语义,表示一种抽象的可生成结果的计算。FutureTask表示的计算是通过Callable实现的,相当于一种可生成结果的Runnable,并处于三种状态:等待运行、正在运行、运行完成。
FutureTask.get()取决于任务的状态,如果任务已经完成,那么get立即返回结果,否则get将阻塞直到任务进入完成状态,然后返回结果或抛出异常。
信号量
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个操作的数量。
Semaphore管理着一组虚拟的许可,许可的初始数量可通过构造函数指定。在执行操作时可以首先获得许可,使用后释放许可。如果没有许可,acquire将阻塞直至有许可。
栅栏
栅栏(Barrier)类似于闭锁,能阻塞一组线程直到某个事件发生。栅栏的区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,栅栏用于等待其他线程。例如:所有人6:00在麦当劳碰头,到了以后要等其它人,人齐了才能走。