美文网首页
理解semaphore

理解semaphore

作者: Easy的幸福 | 来源:发表于2019-10-28 14:56 被阅读0次
    Semaphore(信号量)

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


    image

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

    • init():设置计数器的初始值。
    • down():计数器的值减1;如果此时计数器的值小于0,则当前线程将被阻塞,否则当前线程可以继续执行。
    • up():计数器的值加1;如果此时计数器的值小于或者等于0,则唤醒等待队列中的一个线程,并将其从等待队列中移除。

    在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()。

    信号量是如何保证互斥的?
    • 假设两个线程T1和T2同时访问addOne()方法,当它们同时调用acquire()的时候,由于acquire()是一个原子操作,所以只能有一个线程(假设T1)把信号量里的计数器减为0,另外一个线程(T2)则是将计数器减为-1。对于线程T1,信号量里面的计数器的值是0,大于等于0,所以线程T1会继续执行;对于线程T2,信号量里面的计数器的值是-1,小于0,按照信号量模型里对down()操作的描述,线程T2将被阻塞。所以此时只有线程T1会进入临界区执行count+=1;。
    • 当线程T1执行release()操作,也就是up()操作的时候,信号量里计数器的值是-1,加1之后的值是0,小于等于0,按照信号量模型里对up()操作的描述,此时等待队列中的T2将会被唤醒。于是T2在T1执行完临界区代码之后才获得了进入临界区执行的机会,从而保证了互斥性。
    快速实现一个限流器

    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的一篇文章。

    相关文章

      网友评论

          本文标题:理解semaphore

          本文链接:https://www.haomeiwen.com/subject/parbvctx.html