闭锁
闭锁的作用相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。
CountDownLatch
countDownLatch是一种闭锁的实现,它可以使一个或多个线程等待一组事件发生。countDown方法表示有一个事件发生了,await方法等待计数器达到零,表示所有需要等待的事件都已经发生,如果计数器的值非零,那么await会一直阻塞直到计数器位零,或者等待中的线程中断,或者等待超时。
一个简单例子的用法,两秒之后并发执行10个线程:
public class CountDownLatchDemo implements Runnable {private final CountDownLatch countDownLatch;public CountDownLatchDemo(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic 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交换对象时,这种交换就把这两个对象安全的发布给另一方。
