闭锁

闭锁的作用相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。

CountDownLatch

countDownLatch是一种闭锁的实现,它可以使一个或多个线程等待一组事件发生。countDown方法表示有一个事件发生了,await方法等待计数器达到零,表示所有需要等待的事件都已经发生,如果计数器的值非零,那么await会一直阻塞直到计数器位零,或者等待中的线程中断,或者等待超时。
一个简单例子的用法,两秒之后并发执行10个线程:

  1. public class CountDownLatchDemo implements Runnable {
  2. private final CountDownLatch countDownLatch;
  3. public CountDownLatchDemo(CountDownLatch countDownLatch) {
  4. this.countDownLatch = countDownLatch;
  5. }
  6. @Override
  7. public void run() {
  8. try {
  9. countDownLatch.await();
  10. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. public static void main(String[] args) {
  16. CountDownLatch countDownLatch = new CountDownLatch(1);
  17. CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo(countDownLatch);
  18. for (int i = 0; i < 10; i++) {
  19. new Thread(countDownLatchDemo).start();
  20. }
  21. try {
  22. Thread.sleep(2000);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }finally {
  26. countDownLatch.countDown();
  27. }
  28. }
  29. }

FutureTask

它也可以用作闭锁,通过callable来实现,相当于可生成结果的Runnable,并且可以处于以下三种状态:等待运行,正在运行和运行完成。
Future.get()如果任务已经完成,那么get会立即返回计算结果,否则将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。

  1. public class FutureTaskDemo {
  2. public static void main(String[] args) {
  3. final FutureTask<Boolean> futureTask = new FutureTask<>(() ->{
  4. Thread.sleep(2000);
  5. return true;
  6. });
  7. new Thread(futureTask).start();
  8. try {
  9. Boolean b = futureTask.get();
  10. System.out.println(b);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. } catch (ExecutionException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

信号量

信号量(Semaphore)用于控制同时访问某个特定资源的操作数量,或者同时执行某个制定操作的数量。还可以用于实现某种资源池,或者对容器施加边界。
Semaphore中管理一组虚拟的许可,可通过构造器来制定许可的初始数量,在执行操作时首先获取许可(只要还有剩余的许可),并在使用后释放许可。如果没有许可,那么acquire将阻塞直到有许可可用。release方法用于释放许可。

  1. public class BoundedHashSet<T> {
  2. private final Set<T> set;
  3. private final Semaphore semaphore;
  4. public BoundedHashSet(Set<T> set, Semaphore semaphore) {
  5. this.set = set;
  6. this.semaphore = semaphore;
  7. }
  8. public boolean add(T t) throws InterruptedException {
  9. semaphore.acquire();
  10. boolean isAddSuccess = false;
  11. try {
  12. isAddSuccess = set.add(t);
  13. return isAddSuccess;
  14. } finally {
  15. if (!isAddSuccess) {
  16. semaphore.release();
  17. }
  18. }
  19. }
  20. public boolean remove(T t) {
  21. boolean remove = set.remove(t);
  22. if (remove) {
  23. semaphore.release();
  24. }
  25. return remove;
  26. }
  27. }

栅栏

栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
一种形式的栅栏是CyclicBarrier可以使一定数量的参与方反复的在栅栏位置汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程都将被释放,而栅栏将重置以便下次使用。
另一种形式的栅栏是Exchanger,它是一种两方栅栏,各方在栅栏位置上交换数据。当两方执行不对称的操作时,Exchange会非常有用,例如当一个线程向缓冲区写入数据,而另外一个线程从缓冲区读取数据。这些线程就可以使用Exchanger来汇合,并将满的缓冲区与空的缓冲区交换。当两个线程通过Exchanger交换对象时,这种交换就把这两个对象安全的发布给另一方。