在容器类中,阻塞队列是一种独特的类:它们不仅能作为保存对象的容器,还能协调生产者和消费者等线程之间的控制流,因为take和put方法将阻塞,直到队列达到期望的状态(队列既非空,也非满)。
同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch)。如果这些类还无法满足需要,可以创建自己的同步工具类。
所有的同步工具类都包含一些特定的结构化属性:它们封装了一些状态,这些状态将决定执行同步工具类的线程是继续执行还是等待,此外还提供了一些方法对状态进行操作,以及一些方法用于高效地等待同步工具类进入到预期状态。
Latch相当于一个gate,当Latch到达特定的状态之前,gate是关闭的,此时所有线程将被阻塞,只有Latch到达了特定的状态,线程才能通过gate。
CountDownLatch是Latch的具体实现,CountDownLatch内部维护了一个计数器,初始化CountDownLatch需要指定计数器的初始值。该初始值表示需要等待完成的事件的个数。每调用一次countDouwn方法,表示其中一个事件完成,计数器的值将减一。当计数器减为0时,gate才会打开:
public class TestHarness { /** * 计算nThreads个线程并发执行task所需的时间 */ 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() { public void run() { try { // 直到startGate内部计数器减为0时才能从await中唤醒 startGate.await(); try { task.run(); } finally { // 完成了一件任务后将endGate减1 endGate.countDown(); } } catch (InterruptedException ignored) { } } }; t.start(); } long start = System.nanoTime(); // 将startGate内部的计数器减1, 打开startGate startGate.countDown(); // 直到endGate内部计数器减为0时才能从await中唤醒 endGate.await(); long end = System.nanoTime(); return end - start; } }
FutureTask用于执行任务,其get方法将返回任务的执行结果。FutureTask常用的构造函数为FutureTask(Callable<V> callable),使用Callable封装任务。FutureTask对象具有三种状态:等待运行,正在运行,完成。当FutureTask对象处于已完成状态时调用get方法,get方法将立即返回计算结果,否则get方法会阻塞,知道FutureTask转变为已完成状态。计算完成、抛出异常或者被取消会使得FutureTask的状态变为已完成。FutureTask的常见使用场景是封装一个耗时任务,然后提前开始计算,当需要计算结果时,再调用其get方法,可以减少等待计算完成的时间。
使用FutureTask的例子:
public class Preloader { private final FutureTask<ProductInfo> future = new FutureTask<ProductInfo>(new Callable<ProductInfo>() { public ProductInfo call() throws DataLoadException { // loadProductInfo方法用于加载产品信息, 这是一个耗时操作 return loadProductInfo(); } }); // FutureTask实现了Runnable接口, 因此可以将future对象放进thread中执行 private final Thread thread = new Thread(future); // 提供start方法启动线程, 而不是在构造方法中直接启动是为了防止this逃逸 public void start() { // 启动thread, 执行future thread.start(); } // 当需要计算结果时, 就调用get方法获得产品信息的加载结果. public ProductInfo get() throws DataLoadException, InterruptedException { try { // get方法将阻塞, 直到产品信息加载完成, 或者抛出异常 return future.get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof DataLoadException) throw (DataLoadException) cause; else throw launderThrowable(cause); } } public static RuntimeException launderThrowable(Throwable t) { if (t instanceof RuntimeException) return (RuntimeException) t; else if (t instanceof Error) throw (Error) t; else throw new IllegalStateException("Not unchecked", t); } }
Semaphore用于管理permit,创建Semaphore对象时,需要指定permit的最大个数,调用acquire()方法申请从Semaphore对象中获取一个permit,如果当前Semaphore对象没有可用的permit,线程将被阻塞,知道有可用的permit,调用release()方法将permit放回Semaphore对象。permit不与线程绑定,一个线程申请的permit,可以在另一个线程里release。Semaphore通常用于实现资源池,如数据库连接池。Semaphore也可以用于实现有界的集合。如:
/** * 有界的set集合 */ public class BoundedHashSet<T> { private final Set<T> set; private final Semaphore sem; public BoundedHashSet(int bound) { this.set = Collections.synchronizedSet(new HashSet<T>()); // 设定Semaphore对象中的permit的最大个数 sem = new Semaphore(bound); } public boolean add(T o) throws InterruptedException { // 每次add时就向semaphore对象申请一个permit sem.acquire(); boolean wasAdded = false; try { wasAdded = set.add(o); return wasAdded; } finally { if (!wasAdded) // 当申请失败是release permit sem.release(); } } public boolean remove(Object o) { boolean wasRemoved = set.remove(o); if (wasRemoved) // 成功移除后将permit release sem.release(); return wasRemoved; } }
CyclicBarrier允许一组线程互相等待, 直到该组线程全部到达某个公共屏障点. 创建CyclicBarrier时需要指定线程组中线程的数量. 调用CyclicBarrier对象的await方法, 表示当前线程已到达公共屏障点, 然后等待其他线程到达. 当所有线程到达公共屏障点后, CyclicBarrier对象将释放线程组, 然后重置CyclicBarrier对象的状态. 因此CyclicBarrier对象是可以循环使用的. 如果有线程在等待期间超时或者被中断, 该CyclicBarrier对象被视为已损坏, 随后对await方法的调用都要抛出BrokenBarrierException异常.