Semaphore源码详解

Semaphore简介

Semaphore就是我们常说的信号量,本质就是基于AQS的一个共享锁。对AQS不太了解的可以看我之前写的AQS源码解析的文章AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Condition

Semaphore常常被用作限流器,通过共享锁对资源进行限制。

Semaphore结构

在这里插入图片描述
如上图所示,Semaphore实现了非公平锁和公平锁两个模式。

Semaphore示例

class Pool {
    private static final int MAX_AVAILABLE = 100;
    // 初始化一个信号量,设置为公平锁模式,总资源数为100个
    private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

    public Object getItem() throws InterruptedException {
        // 获取一个信号量
        available.acquire();
        return getNextAvailableItem();
    }

    public void putItem(Object x) {
        if (markAsUnused(x))
            available.release();
    }

   
    protected Object[] items = ...whatever kinds of items being managed
    protected boolean[] used = new boolean[MAX_AVAILABLE];

    protected synchronized Object getNextAvailableItem() {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (!used[i]) {
                used[i] = true;
                return items[i];
            }
        }
        return null; 
    }

    protected synchronized boolean markAsUnused(Object item) {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (item == items[i]) {
                if (used[i]) {
                    used[i] = false;
                    return true;
                } else
                    return false;
            }
        }
        return false;
    }

}

以上代码的场景是池子中有100个资源,线程可以单独申请其中一个,当申请不到的时候会被挂起等待。

Semaphore源码解析

1、初始化

public Semaphore(int permits) {
    //permits为同一时刻容纳的最大线程数
    //默认调用了非公平锁
    sync = new NonfairSync(permits);
}
//也可以在初始化方法中传入fair
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

2、acquire()

//申请锁,可响应中断
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())//如果线程被中断,抛出异常
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//小于0代表申请失败
        doAcquireSharedInterruptibly(arg);
}

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        //将state-=acquires
        int available = getState();
        int remaining = available - acquires;
        //如果remaining<0直接返回
        //如果CAS成功,返回
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

//doAcquireSharedInterruptibly我们在AQS源码详解中,已经详细说过了,不在展开说了
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //加入锁queue中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            //如果在queue中的第二个节点,尝试申请锁
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                   //申请锁成功后,就将node移出queue
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //将线程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

3、release()

public void release() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//如果释放锁成功
        doReleaseShared();//唤醒其他等待线程来争夺锁
        return true;
    }
    return false;
}

protected final boolean tryReleaseShared(int releases) {
    //cas将state+=releases
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

//doReleaseShared()用来唤醒其他线程,我们在AQS源码详解中,已经详细说过了,不在展开说了
private void doReleaseShared() {

    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

Semaphore模式解析

跟ReentranLock一样,Semaphore也支持tryAcquire()、tryAcquire(long timeout,TimeUnit unit)、acquireUninterruptible(),这些方法的源码就不再解析了,跟ReentrantLock的处理方法一模一样,只不过ReentrantLock申请的独占锁,而Semaphore申请的是共享锁。感兴趣的可以看下我对ReentrantLock的源码分析ReentrantLock源码详解
,然后再自己去看Semaphore的源码进行理解

方法 详解
tryAcquire() 尝试获取锁,只获取一次,申请不到就拉倒,抢锁期间不响应中断
tryAcquire(long timeout,TimeUnit unit) 在timeout个unit时间内,申请锁,申请不到就挂起,抢锁期间可响应中端
acquire() 阻塞式申请锁,申请不到就挂起,抢锁期间可响应中断
acquireUninterruptibly 阻塞式申请锁,申请不到就挂起,抢锁期间不响应中断,在抢锁成功后才响应中断

Semaphore整体代码思路还是比较清晰的,如果朋友们能掌握AQS的话,Semaphore更不在话下了,Semaphore就是根据AQS构建的。

如果朋友们对AQS还不太了解的话,可以看我之前写的AQS源码解析AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Condition

posted @ 2021-08-22 10:07  张孟浩Jay  阅读(92)  评论(1编辑  收藏  举报