初识Semaphore:
Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。
Semaphore用于限制可以访问某些资源(物理或逻辑的)的线程数目,他维护了一个许可证集合,有多少资源需要限制就维护多少许可证集合,假如这里有N个资源,那就对应于N个许可证,同一时刻也只能有N个线程访问。一个线程获取许可证就调用acquire方法,用完了释放资源就调用release方法。
Semaphore管理一系列许可。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可这个对象,Semaphore只是维持了一个可获得许可证的数量。
Semaphore 只有3个操作:
构造方法一:Semaphore(int permits) 创建具有给定的许可数和非公平的公平设置的 Semaphore
public Semaphore(int permits) {sync = new NonfairSync(permits);}
构造方法二:Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的 Semaphore
/*** @param fair :是否公平,默认非公平;* false:非公平;true:公平*/public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
acquire() 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}
acquire(int permits) 获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
public void acquire(int permits) throws InterruptedException {if (permits < 0) throw new IllegalArgumentException();sync.acquireSharedInterruptibly(permits);}
acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
public void acquireUninterruptibly(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.acquireShared(permits);}
tryAcquire()尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
public boolean tryAcquire() {return sync.nonfairTryAcquireShared(1) >= 0;}
tryAcquire(long timeout, TimeUnit unit)尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
public boolean tryAcquire(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}
release() 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
public void release() {sync.releaseShared(1);}
release(int permits)释放给定数目的许可,将其返回到信号量
public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);}
hasQueuedThreads() 等待队列里是否还存在等待线程。
public final boolean hasQueuedThreads() {return sync.hasQueuedThreads();}
getQueueLength()获取等待队列里阻塞的线程数。
public final int getQueueLength() {return sync.getQueueLength();}
drainPermits()清空令牌把可用令牌数置为0,返回清空令牌的数量。
public int drainPermits() {return sync.drainPermits();}
availablePermits()返回可用的令牌数量
public int availablePermits() {return sync.getPermits();}
代码示例:
场景分析:相信有车一族最头疼的就是停车了吧,每次到一个景点去停车,都会被保安告诉车位已满,需要等待,并且只能有车离开才能让下一辆车进去;有的车库在门前会显示有几个车位,剩余多少车位。那么这个功能通过代码是如何实现的呢?简易版代码如下: ```java public class SemaphoreTest {private static int count = 20; //停车场同时容纳的车辆8, 此处默认使用非公平的锁,你们可以私下去使用公平锁去试试,就会发现,先到先得 // 写法示例: Semaphore semaphore = new Semaphore(8, true); private static Semaphore semaphore = new Semaphore(8);
private static ExecutorService service = Executors.newFixedThreadPool(count);
public static void main(String[] args) { test(); }
public static void test() {
//模拟100辆车进入停车场for (int i = 0; i < 20; i++) {int num = i + 1;service.execute(() -> {try {System.out.println("车牌 00000" + num + "来到停车场");if (semaphore.availablePermits() == 0) {System.out.println("车位不足,请耐心等待");}semaphore.acquire();System.out.println("车牌 00000" + num + "成功进入停车场");Thread.sleep(new Random().nextInt(10000));System.out.println("车牌 00000" + num + "驶出停车场");semaphore.release();//释放令牌,腾出停车场车位} catch (InterruptedException e) {e.printStackTrace();}});}service.shutdown();
} }
执行结果:> 车牌 000001来到停车场> 车牌 000003来到停车场> 车牌 000002来到停车场> 车牌 000003成功进入停车场> 车牌 000001成功进入停车场> 车牌 000005来到停车场> 车牌 000005成功进入停车场> 车牌 000006来到停车场> 车牌 000006成功进入停车场> 车牌 000004来到停车场> 车牌 000002成功进入停车场> 车牌 000004成功进入停车场> 车牌 000007来到停车场> 车牌 000007成功进入停车场> 车牌 000008来到停车场> 车牌 000008成功进入停车场> ==================================> 车牌 000009来到停车场> 车位不足,请耐心等待> 车牌 0000010来到停车场> 车位不足,请耐心等待> 车牌 0000011来到停车场> 车位不足,请耐心等待> 车牌 0000012来到停车场> 车位不足,请耐心等待> 车牌 0000013来到停车场> 车位不足,请耐心等待> 车牌 0000014来到停车场> 车位不足,请耐心等待> 车牌 0000015来到停车场> 车位不足,请耐心等待> 车牌 0000016来到停车场> 车位不足,请耐心等待> 车牌 0000017来到停车场> 车位不足,请耐心等待> 车牌 0000018来到停车场> 车位不足,请耐心等待> 车牌 0000019来到停车场> 车位不足,请耐心等待> 车牌 0000020来到停车场> 车位不足,请耐心等待> ===================================> 车牌 000004驶出停车场> 车牌 000009成功进入停车场> 车牌 000002驶出停车场> 车牌 0000010成功进入停车场> 车牌 0000010驶出停车场> 车牌 0000011成功进入停车场> 车牌 0000011驶出停车场> 车牌 0000012成功进入停车场> 车牌 000003驶出停车场> 车牌 0000013成功进入停车场> 车牌 000006驶出停车场> 车牌 0000014成功进入停车场> 车牌 000008驶出停车场> 车牌 0000015成功进入停车场> 车牌 000007驶出停车场> 车牌 0000016成功进入停车场> 车牌 0000015驶出停车场> 车牌 0000017成功进入停车场> 车牌 000005驶出停车场> 车牌 0000018成功进入停车场> 车牌 000009驶出停车场> 车牌 0000019成功进入停车场> 车牌 0000019驶出停车场> 车牌 0000020成功进入停车场> 车牌 000001驶出停车场> 车牌 0000012驶出停车场> 车牌 0000017驶出停车场> 车牌 0000016驶出停车场> 车牌 0000018驶出停车场> 车牌 0000013驶出停车场> 车牌 0000014驶出停车场> 车牌 0000020驶出停车场<a name="sm4ko"></a>## 源码解析:(默认使用不公平许可证)<br />通过上面案例,我们可以发现,首先通过 **semaphore.availablePermits() **当前资源是否充足,不足则告诉其他车主,停车场满了别进来了。然后调用 **semaphore.acquire() **去获取令牌,源码如下:```java/*** 获取1个令牌*/public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}/*** 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程*/public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}/*** 1、创建节点,加入阻塞队列,* 2、重双向链表的head,tail节点关系,清空无效节点* 3、挂起当前节点线程*/private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {//创建节点加入阻塞队列final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {//获得当前节点pre节点final Node p = node.predecessor();if (p == head) {//返回锁的stateint r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//重组双向链表,清空无效节点,挂起当前线程if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
获取到了车位,然后开始停车。。。。。。下班了,开车回家,此时我们是不是要离开车库,即去释放车位;
源码如下:
/*** 释放令牌*/public void release() {sync.releaseShared(1);}/*** 释放共享锁,同时唤醒所有阻塞队列共享节点线程*/public final boolean releaseShared(int arg) {//释放共享锁if (tryReleaseShared(arg)) {//唤醒所有共享节点线程doReleaseShared();return true;}return false;}/*** 共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。*/protected final boolean tryReleaseShared(int releases) {for (;;) {int current = getState();int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");if (compareAndSetState(current, next))return true;}}/*** 唤醒所有共享节点线程*/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))//修改状态为初始0continue;unparkSuccessor(h);//唤醒h.nex节点线程}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE));}if (h == head) // loop if head changedbreak;}}
