文章目录
对象的组合
Java监视器模式
-
Java监视器模式会将对象 所有可变状态 封装起来,并由对象自己的内置锁保护,例如Vector和Hashtable使用了监视器模式。
-
进入和退出同步代码块的字节指令也被称为monitorenter和monitorexit。
构建基础模块
同步容器类
-
利用Collections.synchronizedXxx将非线程安全的容器类封装为线程安全的容器类。
-
同步容器类是线程安全的,但在某些复合操作上可能需要额外的客户端加锁来保护。容器上常见的符合操作包括:迭代,跳转(根据当前元素位置找到当前元素的下一个元素),条件运算(例如若没有则添加)。
-
迭代器与ConcurrentModificationException
- 线程安全容器类的迭代操作采取的是**“及时失败”**,如果在容器迭代期间被修改,那么会抛出ConcurrentModificationException异常。这种实现方式是将计数器的变化与容器关联起来:如果在迭代期间计数器被修改,那么hasnext或next会抛出ConcurrentModificationException异常。
- 基于这种设计是为了一种设计上的权衡,从而降低并发修改操作的检测代码对程序性能产生的影响。
- 可以加锁来完成安全的容器遍历,但迭代期间对容器加锁可能会造成其它线程长时间等待。另一种方式是“克隆”容器,并在副本上进行迭代。克隆期间需要对容器加锁,并且克隆容器存在性能开销。
并发容器
-
ConcurrentHashMap 采用分段锁实现,由16个锁来保护散列桶。ConcurrentHashMap 的迭代器不会抛出ConcurrentModificationException异常,ConcurrentHashMap 的迭代器具有弱一致性,而并非及时失败。遍历元素时可以(但不保证)将容器的修改操作返回给容器。
-
CopyOnWriteArrayList 用于替代同步list,迭代期间不需要对容器进行加锁或复制。容器每次修改时,都会创建一个新的容器副本,从而实现可见性。容器迭代的数据与迭代器创建时的数据一样,每当修改容器时都会复制底层数组,这需要一定的开销。仅当迭代操作远多于修改操作时才应该使用“写入时复制”容器。
-
BlockingQueue
阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法。队列可以是有界的或无界的。
-
LinkedBlockingQueue ArrayBlockingQueue
类似于LinkedList,ArrayList。但比同步List具有更好的性能。
-
PriorityBlockingQueue
优先级阻塞队列,可以按照元素自然顺序(如果实现了Comparable方法)来比较元素,或者也可以使用Comparator来比较。
-
SynchronousQueue
不是一个真正的队列,因为它不会为队列中元素维护存储空间。它维护一组线程,这组线程在等待着把元素加入或移出队列。仅当有足够多的消费者,并且总是有一个消费者准备好交付工作时才适合使用同步队列。
-
-
Queue
- ConcurrentLinkedQueue 传统的先进先出队列
- PriorityQueue 非并发的优先队列
-
Deque和BlockingDeque
Deque是一个双端队列,实现在队列头和队列尾高效插入和移除元素。实现有ArrayDeque和LinkedBlockingDeque。
同步工具类
-
闭锁
闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门是一直关闭的,并且没有任何线程能够通过。闭锁到达结束状态后,这扇门会打开并允许所有线程通过,此后不会再改变状态。闭锁可以用于确保某些活动直到其它活动都完成才继续执行。
-
CountDownLatch
闭锁状态包含一个计数器,该计数器被初始化为一个正值,表示需要等待的事件数量。countDown方法递减计数器,await方法等待计数器到达0。
-
-
FutureTask
FutureTask表示一种可生成结果的计算,计算是通过Callable实现的,相当于一种可生成结果的Runnable。FutureTask有三种状态:等待运行,正在运行,运行完成。运行完成表示所有可能的结束方式,包括正常结束,由于取消而结束和由于异常结束。FutureTask.get会阻塞到任务进入完成状态然后获取结果。
-
信号量Semaphore
Semaphore用来控制同时访问某个特定资源或者执行某个特定操作的数量,还可以实现某种资源池或者对容器施加边界。Semaphore初始值为1的话还可以用于互斥体。
Semaphore使用acquire获取资源,使用release释放资源。若资源不足将会阻塞到有资源可用。
-
栅栏
栅栏类似于闭锁,它能阻塞一组线程直到某个时间发生。栅栏与闭锁的关键区别在于所有线程必须同时到达栅栏位置才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程,实现一些协议。
-
CyclicBarrier
-
Exchanger
Exchanger是一种两方栅栏,各方在栅栏位置交换数据。当两方进行不对称的操作时,Exchanger会非常有用。例如当一个线程向缓冲池写入数据,而另一个线程向缓冲池读取数据。这些线程可以使用Exchanger来汇合,并将满的缓冲区与空的缓冲区交换。
-
构建高效可伸缩的缓存
利用FutureTask和ConcurrentHashMap实现一个高效缓存
/**
* 用于将某个计算结果放入到缓存中,如果缓存中有数据则直接取出。
* 使用FutureTask进行异步计算,可同时进行多个计算需求。
*
* @Date 2020/5/25
* @author varg
*/
public class Memoizer<A,V> implements Computable<A,V>{
private final ConcurrentMap<A,