相关文章:
Semaphore,即信号量,是一个同步工具类,用于控制某个资源可被同时访问的线程个数
一、内部类解析
-
Sync
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1192457210091910933L; // Sync 构造方法,初始化许可数目 Sync(int permits) { setState(permits); } // 获取当前许可数目 final int getPermits() { return getState(); } // 非公平模式尝试获取许可 final int nonfairTryAcquireShared(int acquires) { for (;;) { // 获取当前许可数目 int available = getState(); // 减去这次需要获取的许可数目 int remaining = available - acquires; /* * 如果剩余许可数目小于 0,则直接返回; * * 如果剩余许可数目大于等于 0,则使用 CAS 操作将 * 当前许可数目由 available 更新为 remaining, * 并返回剩余许可数目 */ 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"); /* * 如果未溢出,则使用 CAS 操作将 * 当前许可数目由 current 更新为 next, * 并返回 true */ 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"); /* * 如果未溢出,则使用 CAS 操作将 * 当前许可数目由 current 更新为 next, * 并直接返回 */ if (compareAndSetState(current, next)) return; } } // 销毁许可 final int drainPermits() { for (;;) { // 获取当前许可数目 int current = getState(); /* * 如果当前许可数目为 0,则直接返回; * * 如果当前许可数目不为 0,则使用 CAS 操作将 * 当前许可数目由 current 更新为 0, * 并返回当前许可数目 */ if (current == 0 || compareAndSetState(current, 0)) return current; } } }
- Sync 是 Semaphore 的内部类,用于帮助 Semaphore 进行同步控制,其继承自 AQS,并使用 AQS 的同步状态 (state) 来表示许可数目
-
NonfairSync
static final class NonfairSync extends Sync { private static final long serialVersionUID = -2694183684443567898L; // NonfairSync 构造方法,调用父类的构造方法来初始化许可数目 NonfairSync (int permits) { super(permits); } // 尝试获取许可,调用父类的 nonfairTryAcquireShared(int acquires) 方法来获取许可 protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } }
-
非公平模式,NonfairSync 继承自 Sync
-
在使用过程中,会通过调用 Sync 的
nonfairTryAcquireShared(int acquires)
方法来获取许可
-
-
FairSync
static final class FairSync extends Sync { private static final long serialVersionUID = 2014338818796000944L; // FairSync 构造方法,调用父类的构造方法来初始化许可数目 FairSync(int permits) { super(permits); } // 尝试获取许可 protected int tryAcquireShared(int acquires) { for (;;) { // 判断等待队列中有没有比当前线程等待时间更长的线程 (即优先级更高) if (hasQueuedPredecessors()) // 如果有,则返回 -1 return -1; // 如果没有,则继续执行 // 获取当前许可数目 int available = getState(); // 减去这次需要获取的许可数目 int remaining = available - acquires; /* * 如果剩余许可数目小于 0,则直接返回; * * 如果剩余许可数目大于等于 0,则使用 CAS 操作将 * 当前许可数目由 available 更新为 remaining, * 并返回剩余许可数目 */ if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } }
-
公平模式,FairSync 继承自 Sync
-
在使用过程中,会先检测是否有比当前线程优先级更高的线程,如果有,则获取许可失败,进入 CLH 队列;如果没有,则获取许可成功,并使用 CAS 操作更新许可数目
-
二、字段解析
-
sync
private final Sync sync;
- sync 是内部类 Sync 的一个实例
三、构造方法解析
-
Semaphore(int permits)
public Semaphore(int permits) { // 实例化 NonfairSync 对象 (非公平模式) sync = new NonfairSync(permits); }
-
Semaphore(int permits)
public Semaphore(int permits, boolean fair) { /* * 根据 fair 来判断使用公平模式还是非公平模式 * true * 实例化 FairSync 对象 (公平模式) * false * 实例化 NonfairSync 对象 (非公平模式) */ sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
四、方法解析
1、acquire() 相关方法
-
acquire()
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
- 获取许可,阻塞当前线程,直到获取到许可或线程被中断为止
-
acquireSharedInterruptibly(int arg)
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { // 如果线程被中断,则抛出异常 if (Thread.interrupted()) throw new InterruptedException(); // 尝试获取共享锁 if (tryAcquireShared(arg) < 0) // 在可中断模式下获取共享锁 doAcquireSharedInterruptibly(arg); }
- 获取共享锁,如果中断则中止
-
tryAcquireShared(int arg)
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
-
尝试获取共享锁,该方法没有具体实现,交由子类实现,此处实际调用的是 Semaphore 内部类 NonfairSync 中的
tryAcquireShared(int acquires)
方法 (非公平模式) 或内部类 FairSync 中的tryAcquireShared(int acquires)
方法 (公平模式) -
非公平模式,当前线程获取许可,并将许可数目减 1
-
公平模式,如果没有优先级更高的线程,则当前线程获取许可,并将许可数目减 1;如果有优先级更高的线程,则阻塞当前线程,并将其加入 CLH 队列
-
-
doAcquireSharedInterruptibly(int arg)
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { // 创建一个共享节点 node,将其加入到 CLH 队列尾部 final Node node = addWaiter(Node.SHARED); // 标记是否成功获取资源 boolean failed = true; try { for (;;) { // 获取 node 节点的前驱节点 p final Node p = node.predecessor(); // 如果 p 节点为头部节点,则 node 节点会尝试去获取资源 if (p == head) { /* * 此处再次调用了 Semaphore * 内部类 NonfairSync 中的 tryAcquireShared(int acquires) 方法 (非公平模式) * 或内部类 FairSync 中的 tryAcquireShared(int acquires) 方法 (公平模式) */ int r = tryAcquireShared(arg); // 如果剩余许可数目大于等于 0 if (r >= 0) { // 将 node 节点设置为头部节点,并唤醒后继节点 setHeadAndPropagate(node, r); /* * 此时 CLH 队列的头部节点为 node, * 原先的头部节点 p 已处理完资源出队, * 因此将 p 节点的 next 引用设置为 null * 方便 GC 对 P 节点进行回收 */ p.next = null; // help GC failed = false; return; } } /* * 如果 p 节点不为头部节点,则根据 p 节点 * 的状态来判断是否要阻塞 node 节点 */ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { // 如果未成功获取资源,则取消 node 节点获取锁的尝试 if (failed) cancelAcquire(node); } }
- 在可中断模式下获取共享锁
-
addWaiter(Node mode)
private Node addWaiter(Node mode) { /* * 根据当前线程和给定模式,创建一个 Node 节点 * model: SHARED(共享)、EXCLUSIVE(独占) */ Node node = new Node(Thread.currentThread(), mode); // 将 pred 节点的引用指向尾部节点 Node pred = tail; // 如果 pred 节点不为 null if (pred != null) { // 则将 pred 节点设置为 node 节点的前驱节点 node.prev = pred; // 使用 CAS 操作将尾部节点由 pred 节点更新为 node 节点 if (compareAndSetTail(pred, node)) { // 将 node 节点设置为 pred 节点的后继节点 pred.next = node; return node; } } /* * 如果 pred 节点为 null,或上述 * compareAndSetTail(pred, node) 操作失败, * 则调用 enq(final Node node) 方法将 node * 节点加入到 CLH 队列尾部 */ enq(node); return node; }
- 根据当前线程和给定模式,创建一个 Node 节点,将其加入到 CLH 队列尾部
-
enq(final Node node)
private Node enq(final Node node) { for (;;) { // 将 t 节点的引用指向尾部节点 Node t = tail; // 如果 t 节点为 null if (t == null) { /* * 说明队列为空,则创建一个新的 Node * 节点作为队列的头部节点和尾部节点 */ if (compareAndSetHead(new Node())) tail = head; // 如果 t 节点不为 null } else { // 说明队列不为空,则将 t 节点设置为当前节点的前驱节点 node.prev = t; // 使用 CAS 操作将尾部节点由 t 节点更新为当前节点 if (compareAndSetTail(t, node)) { // 将当前节点设置为 t 节点的后继节点 t.next = node; return t; } } } }
- 将当前节点加入到 CLH 队列尾部
-
setHeadAndPropagate(Node node, int propagate)
private void setHeadAndPropagate(Node node, int propagate) { // 将 h 节点的引用指向旧的头部节点 Node h = head; // 将当前节点设置为新的头部节点 setHead(node); /* * 此处 propagate 为 Semaphore 内部类 NonfairSync 中的 * tryAcquireShared(int acquires) 方法 (非公平模式) 的返回值, * 或内部类 FairSync 中的 tryAcquireShared(int acquires) 方法 (公平模式) 的返回值, * 即剩余许可数目,是决定是否传播的依据之一 * * 如果剩余许可数目大于 0,或旧头部节点为 null, * 或旧头部节点状态不为 CANCELLED(1) 或默认状态 (0), * 或新头部节点为 null,或新头部节点状态不为 CANCELLED(1) 或默认状态 (0) */ if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { // 将 s 节点的引用指向当前节点的后继节点 Node s = node.next; /* * 如果 s 节点为 null 或 s 节点为 * 共享节点,则对后继节点进行唤醒传播 */ if (s == null || s.isShared()) doReleaseShared(); } }
- 设置当前节点为头部节点,并根据
tryAcquireShared(int acquires)
方法的返回值以及节点状态来判断是否需要唤醒后继节点
- 设置当前节点为头部节点,并根据
-
doReleaseShared()
private void doReleaseShared() { /* * 如果头部节点存在后继节点,且节点状态为 SIGNAL(-1),则唤醒后继节点; * 如果头部节点存在后继节点,且节点状态为默认状态 (0), * 则为了保证唤醒操作可以正确稳定地传播下去,需要设置头部节点状态为 * PROPAGATE(-3),这样的话,当获取锁的线程在执行 setHeadAndPropagate(Node node, int propagate) * 方法时可以读取到头部节点的 PROPAGATE(-3) 状态,从而让获取锁的线程去唤醒后继节点 */ for (;;) { // 将 h 节点的引用指向头部节点 Node h = head; /* * 如果 h 节点不为 null 且 h 节点 * 不是尾部节点 (说明 h 节点存在后继节点) */ if (h != null && h != tail) { // 获取 h 节点状态 int ws = h.waitStatus; // 如果 h 节点状态为 SIGNAL(-1) if (ws == Node.SIGNAL) { /* 使用 CAS 操作将 h 节点状态由 SIGNAL(-1) * 更新为默认状态 (0),若操作失败则跳过本次循环 */ if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases // 唤醒后继节点 unparkSuccessor(h); } /* * 如果 h 节点状态为默认状态 (0), * 则需要使用 CAS 操作来将 h 节点状态设置为 PROPAGATE(-3), * 用以保证对后继节点进行唤醒传播 */ else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } /* * 如果 h 节点仍然为头部节点,则结束循环 * 如果头部节点已改变,则重新进行循环 */ if (h == head) // loop if head changed break; } }
-
唤醒头部节点的后继节点或设置头部节点状态为传播状态 (PROPAGATE(-3))
-
后继节点被唤醒后,会尝试获取共享锁,获取成功之后,又会调用
setHeadAndPropagate(Node node, int propagate)
方法,将唤醒操作传播下去 -
该方法保证了队列中处于等待状态的节点能够有办法被唤醒
-
-
shouldParkAfterFailedAcquire(Node pred, Node node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前驱节点状态 int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * 如果前驱节点状态为 SIGNAL(-1),则表示其在释放锁的时候 * 会去唤醒后继节点,所以此时后继节点可以阻塞自己,等待被唤醒 */ return true; if (ws > 0) { /* * 如果前驱节点状态为 CANCELLED(1),则在队列中向前遍历, * 直到找到第一个非 CANCELLED(1) 状态的节点,并将该节点 * 设置为当前节点的前驱节点 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 如果前驱节点状态既不为 SIGNAL(-1) 也不为 CANCELLED(1), * 则使用 CAS 操作将前驱节点状态设置为 SIGNAL(-1) */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
- 根据前驱节点状态来判断是否要阻塞当前节点
-
parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() { // 阻塞当前线程 LockSupport.park(this); // 检查当前线程是否被中断 return Thread.interrupted(); }
- 阻塞当前线程并检查其是否被中断
-
cancelAcquire(Node node)
private void cancelAcquire(Node node) { // 忽略当前节点不存在的情况 if (node == null) return; // 将当前节点的线程设置为 null node.thread = null; // 将 pred 节点的引用指向当前节点的前驱节点 Node pred = node.prev; /* * 如果前驱节点状态为 CANCELLED(1),则在队列中向前遍历, * 直到找到第一个非 CANCELLED(1) 状态的节点,并将该节点 * 设置为当前节点的前驱节点 */ while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 将 predNext 节点的引用指向 pred 节点的后继节点 Node predNext = pred.next; // 将当前节点状态设置为 CANCELLED(1) node.waitStatus = Node.CANCELLED; /* * 如果当前节点是尾部节点,且使用 CAS 操作将尾部节点 * 由当前节点更新为 pred 节点,成功后,再使用 CAS 操作 * 将 pred 节点的后继节点由 predNext 节点更新为 null * * 此时就断开了 pred 节点与其所有后继节点的联系,这些后继 * 节点在引用链上不可达,最终会被 GC 回收掉 */ if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); /* * 如果当前节点不是尾部节点 (即当前节点还存在着后继节点), * 此时要做的事是将 pred 节点和当前节点的非 CANCELLED(1) * 状态的后继节点拼接起来 */ } else { int ws; /* * 如果 pred 节点不是头部节点, * 且 (pred 节点状态为 SIGNAL(-1);或如果 pred 节点状态 * 不为 SIGNAL(-1),则将其设置为 SIGNAL(-1)) * 且 pred 节点的线程不为 null */ if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { // 将 next 节点的引用当前节点的后继节点 Node next = node.next; /* * 如果 next 节点不为 null 且 next 节点状态不为 CANCELLED(1) * 则使用 CAS 操作将 pred 节点的后继节点由 predNext 节点更新为 next 节点 */ if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); /* * 如果不满足上诉条件,则在这种情况下, * 为了保证队列的活跃性,需要去唤醒一次后继节点 * * 举例说明: * 如果 pred 节点是头部节点,则有可能当前已经 * 没有线程持有锁了,也就不会有释放锁唤醒后继节点 * 的操作,而如果不唤醒后继节点,队列就挂掉了 */ } else { unparkSuccessor(node); } /* * 将当前节点的后继节点设置为其本身,之所以不设置为 null, * 是因为为了方便 AQS 中 Condition 部分的 isOnSyncQueue 方法 * * isOnSyncQueue: * 用于判断一个原先属于条件队列的节点是否转移到了同步队列上, * 因为同步队列中会用到节点的 next 域,如果节点状态为 CANCELLED(1) * 且其 next 域也有值的话,则可以说明该节点一定位于同步队列上 * * 在 GC 层面,和设置为 null 具有相同的效果 */ node.next = node; // help GC } }
- 取消当前节点获取锁的尝试
-
unparkSuccessor(Node node)
private void unparkSuccessor(Node node) { // 获取当前节点状态 int ws = node.waitStatus; /* * 如果当前节点状态不为 CANCELLED(1) 或默认状态 (0) * 则使用 CAS 操作将当前节点状态设置为默认状态 (0) */ if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 将 s 节点的引用指向当前节点的后继节点 Node s = node.next; // 如果 s 节点为 null 或 s 节点状态为 CANCELLED(1) if (s == null || s.waitStatus > 0) { // 将 s 节点设置为 null s = null; /* * 在队列中向前遍历,直到找到第一个非 CANCELLED(1) * 状态的节点并将 s 节点的引用指向该节点 */ for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 如果 s 节点不为 null,则将其唤醒 if (s != null) LockSupport.unpark(s.thread); }
- 如果当前节点存在后继节点,则将其唤醒
2、acquireUninterruptibly() 相关方法
-
acquireUninterruptibly()
public void acquireUninterruptibly() { sync.acquireShared(1); }
- 获取许可,阻塞当前线程,直到获取到许可或线程被中断为止,忽略中断
3、acquire(int permits) 相关方法
-
acquire(int permits)
public void acquire(int permits) throws InterruptedException { // 如果获取许可数目小于 0,则抛出异常 if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits); }
- 获取指定数目的许可,阻塞当前线程,直到获取到许可或线程被中断为止
4、acquireUninterruptibly(int permits) 相关方法
-
acquireUninterruptibly(int permits)
public void acquireUninterruptibly(int permits) { // 如果获取许可数目小于 0,则抛出异常 if (permits < 0) throw new IllegalArgumentException(); sync.acquireShared(permits); }
- 获取指定数目的许可,阻塞当前线程,直到获取到许可或线程被中断为止,忽略中断
5、tryAcquire() 相关方法
-
tryAcquire()
public boolean tryAcquire() { // 调用内部类 Sync 的 nonfairTryAcquireShared(int acquires) 方法来获取许可 return sync.nonfairTryAcquireShared(1) >= 0; }
- 尝试获取许可,如果获取成功,则返回 true;如果获取失败,则返回 false
6、tryAcquire(long timeout, TimeUnit unit) 相关方法
-
tryAcquire(long timeout, TimeUnit unit)
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
- 尝试获取许可,在指定的等待时间内,如果获取成功,则返回 true;如果获取失败,则返回 false
7、release() 相关方法
-
release()
public void release() { sync.releaseShared(1); }
- 释放许可
-
releaseShared(int arg)
public final boolean releaseShared(int arg) { // 尝试释放共享锁 if (tryReleaseShared(arg)) { // 唤醒后继节点 doReleaseShared(); return true; } return false; }
- 释放共享锁
-
tryReleaseShared(int arg)
protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
- 尝试释放共享锁,该方法没有具体实现,交由子类实现,此处实际调用的是 Semaphore 内部类 Sync 中的
tryReleaseShared(int releases)
方法 (当前线程释放许可,并将许可数目加 1)
- 尝试释放共享锁,该方法没有具体实现,交由子类实现,此处实际调用的是 Semaphore 内部类 Sync 中的
8、release(int permits) 相关方法
-
release(int permits)
public void release(int permits) { // 如果释放许可数目小于 0,则抛出异常 if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); }
- 释放指定数目的许可
9、availablePermits() 相关方法
-
availablePermits()
public int availablePermits() { return sync.getPermits(); }
- 获取当前许可数目
10、drainPermits() 相关方法
-
drainPermits()
public int drainPermits() { return sync.drainPermits(); }
- 获取当前许可数目,并将当前许可数目设置为 0
11、reducePermits(int reduction) 相关方法
-
reducePermits(int reduction)
protected void reducePermits(int reduction) { // 如果减少许可数目小于 0,则抛出异常 if (reduction < 0) throw new IllegalArgumentException(); sync.reducePermits(reduction); }
- 减少当前许可数目
12、isFair() 相关方法
-
isFair()
public boolean isFair() { return sync instanceof FairSync; }
-
判断当前使用的是公平模式还是非公平模式
-
如果使用公平模式,则返回 true;如果使用非公平模式,则返回 false
-
13、hasQueuedThreads() 相关方法
-
hasQueuedThreads()
public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
-
查询是否有线程正在等待获取许可,如果有,则返回 true;如果没有,则返回 false
-
由于线程随时可能会被取消等待,因此返回 true 并不能保证任何其他线程会获取到许可
-
此方法主要用于监视系统状态
-
14、getQueueLength() 相关方法
-
getQueueLength()
public final int getQueueLength() { return sync.getQueueLength(); }
-
获取正在等待获取许可的线程数目的估计值
-
该值只是一个估计值,因为此方法在遍历内部数据结构时,线程数量可能会发生变化
-
此方法主要用于监视系统状态,而不用于同步控制
-
五、举例说明
-
SemaphoreTest.java
public class SemaphoreTest { static int permits = 2; static int count = 0; private void go() { Semaphore semaphore = new Semaphore(permits); for (int i = 0; i < 8; i++) { new Thread(new SemaphoreTestTask(semaphore), "Thread" + i).start(); } } public static void main(String[] args) { new SemaphoreTest().go(); // 线程【Thread0】开始工作 // 线程【Thread1】开始工作 // 线程【Thread1】完成工作 // 线程【Thread0】完成工作 // =============================================== // 线程【Thread4】开始工作 // 线程【Thread5】开始工作 // 线程【Thread5】完成工作 // 线程【Thread4】完成工作 // =============================================== // 线程【Thread2】开始工作 // 线程【Thread3】开始工作 // 线程【Thread3】完成工作 // 线程【Thread2】完成工作 // =============================================== // 线程【Thread6】开始工作 // 线程【Thread7】开始工作 // 线程【Thread6】完成工作 // 线程【Thread7】完成工作 // =============================================== } } class SemaphoreTestTask implements Runnable { private Semaphore semaphore; public SemaphoreTestTask(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("线程【" + Thread.currentThread().getName() + "】开始工作"); TimeUnit.SECONDS.sleep(1); System.out.println("线程【" + Thread.currentThread().getName() + "】完成工作"); if (++SemaphoreTest.count == SemaphoreTest.permits) { System.out.println("==============================================="); SemaphoreTest.count = 0; } TimeUnit.SECONDS.sleep(1); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }
- 如上所示,调用
acquire()
方法会获取许可,获取到了许可的线程才能继续执行,没有获取到许可的线程需要阻塞等待,直到许可被释放为止
- 如上所示,调用