Java并发编程--Semaphore

概述

  信号量(Semaphore)控制同时访问资源的线程数量,支持公平和非公平两种方式获取许可。

使用

  提供的方法

 1 public Semaphore(int permits)    //permits为许可数,默认非公平方式
 2 public Semaphore(int permits, boolean fair)
 3 
 4 //获取一个许可。若获取成功,permits-1,直接返回;否则当前线程阻塞直到有permits被释放,除非线程被中断
 5 //如果线程被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。 
 6 public void acquire() throws InterruptedException
 7 //忽略中断
 8 public void acquireUninterruptibly()
 9 //尝试获取一个许可,成功返回true,否则返回false。
10 //即使已将此信号量设置为使用公平排序策略,但是调用 tryAcquire() 也将 立即获取许可(如果有一个可用),而不管当前是否有正在等待的线程。
11 public boolean tryAcquire()
12 //超时尝试获取一个许可,该方法遵循公平设置
13 public boolean tryAcquire(long timeout, TimeUnit unit)
14 //释放一个许可
15 public void release()
16 
17 //以上方法都是获取或释放一个许可,每个方法都存在对应的获取或释放指定个数许可的方法。例如public boolean tryAcquire(int permits)

  使用示例:

    使用信号量实现对内容池(例如线程池)的访问。

 1 class Pool {
 2     private static final int MAX_AVAILABLE = 100;    //许可数为100,在本例中也是内容池的item的个数。
 3     private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
 4 
 5     //获取池中的一个item,首先从Semaphore获取许可;获取许可成功后,从池中获取一个可用的item,并把该item标记为已使用。
 6     public Object getItem() throws InterruptedException {
 7         available.acquire();
 8         return getNextAvailableItem();
 9     }
10 
11     //将指定的item释放到池中,如果markAsUnused返回true,释放Semaphore的一个许可。
12     public void putItem(Object x) {
13         if (markAsUnused(x))
14             available.release();
15     }
16 
17     // Not a particularly efficient data structure; just for demo
18 
19     protected Object[] items = ... whatever kinds of items being managed    //内容池,例如:连接池,每个item代表一个连接。
20     protected boolean[] used = new boolean[MAX_AVAILABLE];    //标记池中的每个item是否已经被占用
21 
22     protected synchronized Object getNextAvailableItem() {
23         for (int i = 0; i < MAX_AVAILABLE; ++i) {
24             if (!used[i]) {
25                 used[i] = true;
26                 return items[i];
27             }
28         }
29         return null; // not reached
30     }
31 
32     protected synchronized boolean markAsUnused(Object item) {
33         for (int i = 0; i < MAX_AVAILABLE; ++i) {
34             if (item == items[i]) {
35                 if (used[i]) {
36                     used[i] = false;
37                     return true;
38                 } else
39                     return false;
40             }
41         }
42         return false;
43     }
44 }

实现原理

  基于AQS实现,用同步状态(state)表示许可数(permits),使用AQS的共享式获取和释放同步状态来实现permits的获取和释放。

  域

1 private final Sync sync;

 

    Sync是Semaphore的抽象内部类,继承了AQS。它有两个子类NonfairSync和FairSync,分别是非公平同步器和公平同步器。

    Sync的源码:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

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

    final int getPermits() {
        return getState();
    }
    
    //非公平共享式获取同步状态。自旋CAS获取同步状态直到成功或许可不足。
    //返回值语义:负数代表获取失败、0代表获取成功但没有剩余资源、正数代表获取成功,还有剩余资源。
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining)) //此处CAS时有可能同步队列中已有等待的线程,就导致的不公平性
                return remaining;
        }
    }
    
    //自定义共享式释放同步状态。自旋CAS释放同步状态直到成功,除非overflow
    //返回值语义:true表示成功,不可能释放失败,除非overflow
    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;
        }
    }
}

 

    NonfairSync(非公平)源码:

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

    FairSync(公平)源码:

 1 static final class FairSync extends Sync {
 2     private static final long serialVersionUID = 2014338818796000944L;
 3 
 4     FairSync(int permits) {
 5         super(permits);
 6     }
 7     //自定义共享式释放同步状态。
 8     protected int tryAcquireShared(int acquires) {
 9         for (;;) {
10             //首先检查同步队列中是否有前驱。如果有则返回失败,将当前线程加入到同步队列的尾部,保证先尝试获取同步状态的线程先成功。
11             if (hasQueuedPredecessors())
12                 return -1;
13             int available = getState();
14             int remaining = available - acquires;
15             if (remaining < 0 ||
16                 compareAndSetState(available, remaining))
17                 return remaining;
18         }
19     }
20 }

  方法

    除tryAcquire外,都是通过调用AQS提供的方法实现获取失败时的阻塞和唤醒机制,具体策略建AQS的源码。

 1 //阻塞式获取一个许可,响应中断
 2 public void acquire() throws InterruptedException {
 3     sync.acquireSharedInterruptibly(1);    //调用AQS提供的可响应中断共享式获取同步状态方法
 4 }
 5 
 6 //阻塞式获取一个许可,忽略中断
 7 public void acquireUninterruptibly() {
 8     sync.acquireShared(1);
 9 }
10 
11 //非阻塞式获取一个许可
12 public boolean tryAcquire() {
13     return sync.nonfairTryAcquireShared(1) >= 0;
14 }
15 
16 //释放一个许可
17 public void release() {
18     sync.releaseShared(1);
19 }

 

 

参考资料

  JDK DOC

  《Java并发编程的艺术》

 

posted @ 2017-11-05 12:12  在周末  阅读(298)  评论(0编辑  收藏  举报