Java 8 - Semaphore 浅析

222 阅读2分钟

在 Java SDK 中,Semaphore 是信号量模型的具体实现。

操作系统中的信号量模型

操作系统中的信号量模型,可概括为 “一个计数器,一个等待队列,三个方法”: image.png

  • init():设置计数器的初始值;
  • down():计数器的值减 1;
    • 当计数器值 < 0,阻塞当前线程;
    • 当计数器值 >= 0,不做其他操作。
  • up():计数器的值加 1;
    • 当计数器值 <= 0,唤醒等待队列中的一个线程,并将其移出队列;
    • 否则,不做其他操作。

这些方法都是原子性的,其原子性是由信号量模型的实现方保证的。

其中 down()、up() 在历史上最早称为 P 操作和 V 操作,所以信号量模型也被称为 PV 原语

信号量模型中,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们。

Semaphore 类

public class Semaphore implements java.io.Serializable { 
    
    private final Sync sync; 
    abstract static class Sync extends AbstractQueuedSynchronizer { ... } 
    
    static final class NonfairSync extends Sync { ... } 
    static final class FairSync extends Sync { ... } 
    
    public Semaphore(int permits) { 
        sync = new NonfairSync(permits); 
    } 
    public Semaphore(int permits, boolean fair) { 
        sync = fair ? new FairSync(permits) : new NonfairSync(permits); 
    } 
    
    // ... 
}

说明:

  • AQS 的上层应用(借助 state 维护许可数量)

当作为互斥锁使用时:

  • 可重入
  • 公平性可选(默认是非公平)

方法:(建议通过 IDE,看看 Semaphore 的 Structure)

  • acquire、acquireUninterruptibly、tryAcquire:申请资源(获取许可证)
  • release:释放资源(释放许可)

应用场景

  1. 通常用于限制访问某些资源的线程数量。如:在限流场景下的限流器,用于连接池、对象池、线程池等。

(在 Semaphore 的 Java Doc 中给了一个 “使用信号量来控制对项目池的访问的类” 的示例)

  1. 当 Semaphore 的计数器为 1 时,可以用作互斥锁。这通常被称为 “二进制信号量”,因为它只有两种状态(可用 or 不可用)。因为信号量没有所有权的概念,这个 “锁” 可以由所有者以外的线程释放。这在一些特殊的环境中很有用,例如死锁恢复。

Note

  1. 因为 Semaphore 允许多个线程访问临界区,所以在临界区内的公共资源,还是要考虑线程安全性。有必要的话,还需要互斥(加锁)操作。

如:在对象池示例中,对象保存在了 Vector 中,Vector 是 Java 提供的线程安全的容器,如果我们把 Vector 换成 ArrayList,是否可以呢?

image.png

不可以! 因为 Semaphore 允许多个线程进入临界区,所以要保证 线程池 add、remove 的安全性,需要使用线程安全的容器进行管理。

参考