同步容器类
同步容器类实现线程安全的方式:将所有状态封装起来,对每个公有方法使用同步,使得每一次只有一个线程可以访问。同步容器类包含:Vector、Hashtable、Collections.synchronizedXXX等
** 同步容器类的问题 **
- 复合操作原子性需要客户端来加锁控制
- 每个操作都持有一个锁,容器规模较大的情况下,持有锁时间过长,锁竞争比较激烈,会降低系统吞吐量和CPU的利用率
并发容器
ConcurrentHashMap
- 通过粒度更细的分段锁实现数据共享
- size和isEmpty等需要在整个Map上计算的方法,返回结果是预估值
- 一些常见的操作在ConcurrentMap接口中已经声明,可以直接使用。如:“若没有则添加”、“若相等则移除”、“若相等则替换”
CopyOnWriteArrayList
- 写入时复制(Copy-On-Write)的线程安全性:初始化一个事实不可变的对象。
- 迭代中不会抛出ConcurrentModificationException
- 比较适合事件通知系统:在分发通知时需要迭代已注册监听器链表,并调用每个监听器,在大多数情况下,注册和注销事件监听器的操作远远小于接受事件通知的操作
阻塞方法与中断方法
线程阻塞原因一般包含:I/O等待、加锁等待、Thread.sleep、等待另一个线程计算结果
线程阻塞状态:BLOCKED、WAITING、TIMED_WAITING
线程中断:Thread.interrupt()可以查询线程中断状态。
同步工具类
** 闭锁 **
闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成
- 闭锁是一次性对象,一旦进入终止状态,就不能被重置。
CountDownLatch - CountDownLatch是JDK 5+里面闭锁的一个实现,允许一个或者多个线程等待某个事件的发生。
CountDownLatch有一个正数计数器,countDown方法对计数器做减操作,await方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时。
FutureTask
- FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法,提空 start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成,那么计算就不能再次启动或是取消。
- FutureTask也可作为闭锁。
** 信号量 **
- 计数信号量用来控制同时访问资源的操作数量、同时执行某个操作的数量。
- 跟锁机制存在一定的相似性,semaphore也是一种锁机制,所不同的是,reentrantLock是只允许一个线程获得锁,而信号量持有多个许可(permits),允许多个线程获得许可并执行。可以用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。
- Semaphore初始值设置为1,可用作互斥锁
** 栅栏 **
- 栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。 栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
构建高效且可伸缩的结果缓存
public interface Computable<A, V> {
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computable<String, BigInteger> {
@Override
public BigInteger compute(String arg) throws InterruptedException {
// 在经过长时间的计算后
return new BigInteger(arg);
}
}
public class Memoizer<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) {
this.c = c;
}
public V compute(A arg) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
}