Java并发编程——Semaphore

1. 简介

前面我们针对CountDownLatch和CyclicBarrier进行了学习,CountDownLatch用于帮助我们实现“倒计时”的功能,当count=0的时候则触发await的线程进行执行。而CyclicBarrier则适用于到达某一状态后接着执行下一步操作的情况。本次介绍的Semaphore是一个计信号数量。

Semaphore通过acquire()方法申请信号量,如果当前无信号量可用,则线程处于阻塞状态,如果有可用信号量,线程正常进行。release方法用于释放一个“锁定占用”的信号量。

Semaphore 通常用于限制访问某些资源(物理或逻辑的)的线程数目。

下面看一个jdk中带的例子,该例子中展示了使用Semaphone控制数据的访问。

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

2、Api分析

Semaphore提供了两个关键的方法acquire()和release()方法。分别用于申请信号量和释放信号量。

如下面示例代码所示:

public class FirstDemo {

    static Semaphore semaphore = new Semaphore(2);
    /**
     * @param args
     */
    public static void main(String[] args) {
        for(int i=0;i<4;i++){
            new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " ready");
                    try {
                        semaphore.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " go");
                    semaphore.release();
                }
            }.start();
        }
    }
}

执行结果:

Thread-0 ready
Thread-0 go
Thread-2 ready
Thread-2 go
Thread-1 ready
Thread-3 ready
Thread-1 go
Thread-3 go

从执行结果来看,最多只能连续执行go两次,间接的印证最多同时只有两个信号量在工作。补充一个特殊情况,当信号量Semaphore = 1 时,它可以当作互斥锁使用。其中0、1就相当于它的状态,当=1时表示其他线程可以获取,当=0时,排他,即其他线程必须要等待。

3. 源码解析

在源码中,Semaphore同样包含一个继承AQS的子类Sync。

public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    /** All mechanics via AbstractQueuedSynchronizer subclass */
    private final Sync sync;

    /**
     * Synchronization implementation for semaphore.  Uses AQS state
     * to represent permits. Subclassed into fair and nonfair
     * versions.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        protected final boolean tryReleaseShared(int 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;
            }
        }

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }
    ....
}

通过使用AQS类的state值来存储信号量,原理类似于CountDownLatch,都是通过AQS的state值进行计数。

创建一个Semaphore对象可以通过构造方法:

/**指定信号量个数*/
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

/**
 * Creates a {@code Semaphore} with the given number of
 * permits and the given fairness setting.
 *
 * @param permits the initial number of permits available.
 *        This value may be negative, in which case releases
 *        must occur before any acquires will be granted.
 * @param fair {@code true} if this semaphore will guarantee
 *        first-in first-out granting of permits under contention,
 *        else {@code false}
 */
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

通过上面的构造函数,可以看到默认情况下Semaphore使用的非公平同步状态,同样也可以通过构造函数进行指定状态。

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)
        doAcquireSharedInterruptibly(arg);
}

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

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

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

在acquire方法中,调用sync的acquireSharedInterruptibly(1)方法,如果线程被iterrupted则抛出 InterruptedException异常。通过tryAcquireShared(arg) 获取状态值,最关键的代码是:

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

获取当前的state值,如果可用state值-1后小于0,即当前信号量被占满,则返回一个负数,然后执行doAcquireSharedInterruptibly()方法添加到等待队列。

4. 总结

无论ReentrantLock或CyclicBarrier内部都是基于AQS进行实现,实现的原理有所差异而已。

推荐阅读更多精彩内容