Semaphore源码解析

197 阅读3分钟

上一期介绍了CyclicBarrier的源码解析,今天来聊一聊最后一个工具:信号量 Semaphore。Semaphore用来控制同时操作某个资源的操作数量。Semaphore管理着permits,每当一个线程来获取许可时,permits数减1,当permits数小于0时,再来获取许可的资源就需要阻塞。下面来详细看看Semaphore的实现原理。

内部类

Semaphore支持公平和非公平两种实现。

//非公平版本
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
		//permits值最终赋值给了AQS的state值
    NonfairSync(int permits) {
      	super(permits);
    }

    protected int tryAcquireShared(int acquires) {
      	ret	urn nonfairTryAcquireShared(acquires);
    }
}
//公平版本
static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    FairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        for (;;) {
        		//可以看出,公平版本的acquire需要先去判断前面是否有排队的
            if (hasQueuedPredecessors())
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

构造方法

permits表示许可的数量,需要在创建Semaphore的时候赋值,Semaphore默认为非公平的。

如果要使用公平的,则需要指定fair参数为true。

public c(int permits) {
  	sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
	sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

方法

acquire

acquire方法调用了Sync的acquireSharedInterruptibly方法

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared方法在Semaphore中分为公平和非公平的两种方式,公平的方式会先去判断前面有没有排队获取许可的线程,如果有说明许可是小于0的,所以直接返回-1。之后公平和非公平的操作是相同的,都是获取state值,减1,通过cas的方式更新state值,返回更新后的值。

tryAcquireShared返回的值如果小于0,说明许可值不够了,需要去排队等待。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //把SHARED的node加到队列尾部
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            //如果node的prev是head,则去尝试获取许可
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                //获取许可成功,把node设置为head,跳出循环
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //node的prev不是head的话,则挂起线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
release

release方法释放一个许可,把它归还给Semaphore,然后把可用的许可(permits)数目加1。

release额方法调用了AQS的releaseShared方法

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared方法通过cas的方式把当前的state值更新为state+arg。

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;
    }
}

doReleaseShared的操作在CountDownLatch的countDown方法中也有用到,大家可以去参照countDown方法

示例

Semaphore的源码中给出了使用的实例,大家可以参考,毕竟官方的肯定是很有指导意义的。

class Pool {
    private static final int MAX_AVAILABLE = 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();
    }

    // Not a particularly efficient data structure; just for demo

    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; // not reached
    }

    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;
    }
}