Semaphore信号量

2020-10-14  本文已影响0人  黄殿玺

Semaphore信号量

最多允许N个线程同时执行,如果信号量的许可设置为1与ReentrantLock效果一致。

以下示例重点是Semaphore的基本使用,忽略CountDownLatch。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 信号量
 * 
 * @author Administrator
 * @date 2020年10月15日
 */
public class Semaphore_test {
    
    static int count = 0;
    
    //并发许可为 5
    static Semaphore s = new Semaphore(5);  //如果许可设置为1与ReentrantLock效果一致。
    
    static CountDownLatch c = new CountDownLatch(10);   //闭锁
    
    public static void main(String[] args) {
        for(int i=0; i<10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1); //方便模拟出count是线程不安全的状态值
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count();
                
                c.countDown();
            }).start();
        }
        
        try {
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println(count);
    }
    
    //count方法最多5个线程同时执行
    public static void count() {
        try {
            s.acquire();
            for(int i=0; i<1000; i++) {
                count++;
            }
        } catch (InterruptedException e) {
        
        }finally {
            s.release();
        }
    }
}

信号量主要的两个方法获取许可acquire方法和释放许可release方法。还有一个有参构造方法。

public Semaphore(int permits) 构造方法,这个方法跟到最后,就是将permits许可数设置成 aqs中的state状态值。

猜想那么这个值在Semaphore中就可以理解为一个阈值。临界区执行的线程数达到阈值了就不允许线程在进入临界区执行了。

/**
 * The synchronization state.
 */
private volatile int state;

acquire

acquire() 方法,一直跟到 .Semaphore .Sync #acquireSharedInterruptibly方法

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)  //获取许可
        doAcquireSharedInterruptibly(arg); //将线程加入到锁池,然后阻塞线程
}

tryAcquireShared方法是aqs的模板方法,被.Semaphore .NonfairSync类重写,以下是重写方法。

此方法通过自旋+cas将state减一,然后返回剩余的许可数量。如果剩余的许可数量小于0说明没有许可了,就会调用doAcquireSharedInterruptibly方法

final int nonfairTryAcquireShared(int acquires) {
    //自旋,不断的取值,不断的cas,直到将state成功减一。
    for (;;) {
        int available = getState(); //取出aqs的状态属性state,该值在Semaphore初始化的时候指定的阈值(许可数量)
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))   //cas操作
            return remaining;
    }
}

doAcquireSharedInterruptibly方法是将线程加入到锁池,然后阻塞线程

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);   //将线程加入锁池
    boolean failed = true;
    try {
        for (;;) {
            //1.取出node的前置节点
            final Node p = node.predecessor();  
            //2.然后判断这个node的前置节点是不是aqs中的头节点,如果是头节点说明马上就到当前这个node节点了,所以需要在尝试获取一下状态(state)值(许可数量),因为在这个过程中很有可能aqs那个头节点已经release了。
            if (p == head) {
                int r = tryAcquireShared(arg);  //3.如果获取到了许可那么就不需要将线程阻塞了
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //如果不符合第一个if判断,再判断是否需要挂起等待,如果需要阻塞线程。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

release() 方法


上一篇下一篇

猜你喜欢

热点阅读