理解semaphore

2019-10-28  本文已影响0人  Easy的幸福
Semaphore(信号量)

信号量可以简单的概括为:一个计数器,一个等待队列,三个方法。在信号量模型里,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们,这三个方法分别是:init()、down()和up()。


image

这三个方法详细的语义具体如下所示:

在Java SDK里面,信号量模型是由java.util.concurrent.Semaphore实现的,Semaphore这个类能够保证这三个方法都是原子操作。

class Semaphore{
  // 计数器
  int count;
  // 等待队列
  Queue queue;
  // 初始化操作
  Semaphore(int c){
    this.count=c;
  }
  // 
  void down(){
    this.count--;
    if(this.count<0){
      //将当前线程插入等待队列
      //阻塞当前线程
    }
  }
  void up(){
    this.count++;
    if(this.count<=0) {
      //移除等待队列中的某个线程T
      //唤醒线程T
    }
  }
}

down()、up()这两个操作历史上最早称为P操作和V操作,所以信号量模型也被称为PV原语。在Java SDK并发包里,down()和up()对应的则是acquire()和release()。

信号量是如何保证互斥的?
快速实现一个限流器

Semaphore除了能像lock一样实现互斥锁之外,还可以允许多个线程访问同一个临界区。(这个功能是lock不容易实现的)

eg:如连接池、对象池、线程池等等。数据库连接池,在同一时刻,一定是允许多个线程同时使用连接池的,当然,每个连接在被释放前,是不允许其他线程使用的。

下面是一个常量池的一个实现代码:

class ObjPool<T, R> {
  final List<T> pool;
  // 用信号量实现限流器
  final Semaphore sem;
  // 构造函数
  ObjPool(int size, T t){
    pool = new Vector<T>(){};
    for(int i=0; i<size; i++){
      pool.add(t);
    }
    sem = new Semaphore(size);
  }
  // 利用对象池的对象,调用func
  R exec(Function<T,R> func) {
    T t = null;
    sem.acquire();
    try {
      t = pool.remove(0);
      return func.apply(t);
    } finally {
      pool.add(t);
      sem.release();
    }
  }
}
// 创建对象池
ObjPool<Long, String> pool = 
  new ObjPool<Long, String>(10, 2);
// 通过对象池获取t,之后执行  
pool.exec(t -> {
    System.out.println(t);
    return t.toString();
});

总结一下:Semaphore就像旋转寿司店,共有10个座位,当座位有空余时,等待的人就可以坐上去。如果有只有2个空位,来的是一家3口,那就只有等待。如果来的是一对情侣,就可以直接坐上去吃。当然如果同时空出5个空位,那一家3口和一对情侣可以同时上去吃。CountDownLatch就像大型商场里面的临时游乐场,每一场游乐的时间过后等待的人同时进场玩,而一场中间会有不爱玩了的人随时出来,但不能进入,一旦所有进入的人都出来了,新一批人就可以同时进场

他们与非共享最大的区别就在于是否能多个线程同时获取锁。

这里有semaphore和countDownlatch的一篇文章。

上一篇 下一篇

猜你喜欢

热点阅读