信号量

除了锁之外,并发API也支持计数的信号量。不过锁通常用于变量或资源的互斥访问,信号量可以维护整体的准入许可。这在一些不同场景下,例如你需要限制你程序某个部分的并发访问总数时非常实用。

下面是一个例子,演示了如何限制对通过sleep(5)模拟的长时间运行任务的访问:

ExecutorService executor = Executors.newFixedThreadPool(10);
Semaphore semaphore = new Semaphore(5);
Runnable longRunningTask = () -> {
    boolean permit = false;
    try {
        permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
        if (permit) {
            System.out.println("Semaphore acquired");
            sleep(5);
        } else {
            System.out.println("Could not acquire semaphore");
        }
    } catch (InterruptedException e) {
        throw new IllegalStateException(e);
    } finally {
        if (permit) {
            semaphore.release();
        }
    }
}
IntStream.range(0, 10)
    .forEach(i -> executor.submit(longRunningTask));
stop(executor);

执行器可能同时运行10个任务,但是我们使用了大小为5的信号量,所以将并发访问限制为5。使用try-finally代码块在异常情况中合理释放信号量十分重要。执行上述代码产生如下结果:

Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore

信号量限制对通过sleep(5)模拟的长时间运行任务的访问,最大5个线程。每个随后的tryAcquire()调用在经过最大为一秒的等待超时之后,会向控制台打印不能获取信号量的结果。

这就是我的系列并发教程的第二部分。以后会放出更多的部分,所以敬请等待吧。像以前一样,你可以在Github上找到这篇文档的所有示例代码,所以请随意fork这个仓库,并自己尝试它。