闭锁
闭锁的作用相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。
CountDownLatch
countDownLatch是一种闭锁的实现,它可以使一个或多个线程等待一组事件发生。countDown方法表示有一个事件发生了,await方法等待计数器达到零,表示所有需要等待的事件都已经发生,如果计数器的值非零,那么await会一直阻塞直到计数器位零,或者等待中的线程中断,或者等待超时。
一个简单例子的用法,两秒之后并发执行10个线程:
public class CountDownLatchDemo implements Runnable {
private final CountDownLatch countDownLatch;
public CountDownLatchDemo(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
countDownLatch.await();
System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo(countDownLatch);
for (int i = 0; i < 10; i++) {
new Thread(countDownLatchDemo).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}
}
FutureTask
它也可以用作闭锁,通过callable来实现,相当于可生成结果的Runnable,并且可以处于以下三种状态:等待运行,正在运行和运行完成。
Future.get()如果任务已经完成,那么get会立即返回计算结果,否则将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。
public class FutureTaskDemo {
public static void main(String[] args) {
final FutureTask<Boolean> futureTask = new FutureTask<>(() ->{
Thread.sleep(2000);
return true;
});
new Thread(futureTask).start();
try {
Boolean b = futureTask.get();
System.out.println(b);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
信号量
信号量(Semaphore)用于控制同时访问某个特定资源的操作数量,或者同时执行某个制定操作的数量。还可以用于实现某种资源池,或者对容器施加边界。
Semaphore中管理一组虚拟的许可,可通过构造器来制定许可的初始数量,在执行操作时首先获取许可(只要还有剩余的许可),并在使用后释放许可。如果没有许可,那么acquire将阻塞直到有许可可用。release方法用于释放许可。
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore semaphore;
public BoundedHashSet(Set<T> set, Semaphore semaphore) {
this.set = set;
this.semaphore = semaphore;
}
public boolean add(T t) throws InterruptedException {
semaphore.acquire();
boolean isAddSuccess = false;
try {
isAddSuccess = set.add(t);
return isAddSuccess;
} finally {
if (!isAddSuccess) {
semaphore.release();
}
}
}
public boolean remove(T t) {
boolean remove = set.remove(t);
if (remove) {
semaphore.release();
}
return remove;
}
}
栅栏
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
一种形式的栅栏是CyclicBarrier可以使一定数量的参与方反复的在栅栏位置汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程都将被释放,而栅栏将重置以便下次使用。
另一种形式的栅栏是Exchanger,它是一种两方栅栏,各方在栅栏位置上交换数据。当两方执行不对称的操作时,Exchange会非常有用,例如当一个线程向缓冲区写入数据,而另外一个线程从缓冲区读取数据。这些线程就可以使用Exchanger来汇合,并将满的缓冲区与空的缓冲区交换。当两个线程通过Exchanger交换对象时,这种交换就把这两个对象安全的发布给另一方。