同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。
阻塞队列
保存对象的容器, 还能协调生产者和消费者等线程之间的控制流 take和put等方法将阻塞,直到队列达到期望的状态(队列即非空,也非满)。
闭锁
相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有线程通过。
到达结束状态后,将不会再改变状态,这扇门永远保持打开状态。
CountDownLatch(倒计数锁存器) 是一种灵活的闭锁实现
countDown方法递减计数器
await方法等待计数器达到零,阻塞直到计数器为零或者等待中的线程中断,或者等待超时
使用场景
示例:
public class TestHarness {
public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);//起始门
final CountDownLatch endGate = new CountDownLatch(nThreads);//结束门
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread(){
@Override
public void run() {
try {
startGate.await();//一直阻塞直到计数器为零
try {
task.run();
}finally {
endGate.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
FutureTask
也可以用作闭锁。
信号量
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。还可以用来实现某种资源池,或者对容器加边界。
Semaphore中管理者一组虚拟的许可,在执行操作时可以首先获得许可,并在使用以后释放许可,如果没有许可,那么acquire将阻塞直到有许可(或者直到中断或者操作超时)。
release方法将返回一个许可给信号量,计算信号量的一种简化形式是二值信号量,即初始值为1的Semaphore。二值信号量可以用作互斥体,并具备不可重入的加锁语义:谁拥有这个唯一的许可,谁就拥有了互斥锁。
Semaphore可以用于实现资源池,例如数据库连接池。
使用Semaphore为容器设置边界:
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound) {
this.set = Collections.synchronizedSet(new HashSet<T>());
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException{
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
} finally {
if (!wasAdded)
sem.release();
}
}
public boolean remove(Object o) {
boolean wasRemoved = set.remove(o);
if (wasRemoved){
System.out.println(Thread.currentThread().getName()+" remove "+o);
sem.release();
}
return wasRemoved;
}
}
栅栏
栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生。
闭锁是一次性对象,一旦进入终止状态,就不能被重置。
栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
CyclicBarrier可以使一定数量的参与方反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。
当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。