JUC同步锁原理源码解析四—-Semaphore
Semaphore
1.Semaphore的来源
A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each {@link #acquire} blocks if necessary until a permit isavailable, and then takes it. Each {@link #release} adds a permit,potentially releasing a blocking acquirer.
一组数量的信号,只有获取到信号的线程才允许执行。通过acquire进行获取,如果获取不到则需要阻塞等待直到一个信号可用。release会释放一个信号量。通过这种方式可以实现限流。
2.Semaphore的底层实现
Semaphore的底层实现依旧依赖于AQS的共享锁机制。
2.AQS源码
Node节点
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
AbstractQueuedSynchronizer类
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;//最重要的一个变量
}
ConditionObject类
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
}
accquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取锁失败,添加到队列中,由于ReentrantLock是独占锁所以节点必须是EXCLUSIVE类型
selfInterrupt();//添加中断标识位
}
addWaiter方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//新建节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//获取到尾指针
if (pred != null) {//尾指针不等于空,将当前节点替换为尾指针
node.prev = pred;
if (compareAndSetTail(pred, node)) {//采用尾插法,充分利用时间局部性和空间局部性。尾插的节点一般不容易被取消。
pred.next = node;
return node;
}
}
enq(node);//cas失败后执行入队操作,继续尝试
return node;
}
enq方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;//获取尾指针
if (t == null) { //代表当前队列没有节点
if (compareAndSetHead(new Node()))//将当前节点置为头结点
tail = head;
} else {//当前队列有节点
node.prev = t;//
if (compareAndSetTail(t, node)) {//将当前节点置为尾结点
t.next = node;
return t;
}
}
}
}
acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//找到当前节点的前驱节点
if (p == head && tryAcquire(arg)) {//前驱节点等于头节点尝试cas抢锁。
setHead(node);//抢锁成功将当前节点设置为头节点
p.next = null; // help GC 当头结点置空
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&//当队列中有节点在等待,判断是否应该阻塞
parkAndCheckInterrupt())//阻塞等待,检查中断标识位
interrupted = true;//将中断标识位置为true
}
} finally {
if (failed)//
cancelAcquire(node);//取消当前节点
}
}
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)//当前节点为空直接返回
return;
node.thread = null;//要取消了将当前节点的线程置为空
// Skip cancelled predecessors
Node pred = node.prev;//获取到当前节点的前驱节点
while (pred.waitStatus > 0)//如果当前节点的前驱节点的状态大于0,代表是取消状态,一直找到不是取消状态的节点
node.prev = pred = pred.prev;
Node predNext = pred.next;//将当前要取消的节点断链
node.waitStatus = Node.CANCELLED;//将当前节点的等待状态置为CANCELLED
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {//如果当前节点是尾结点,将尾结点替换为浅语节点
compareAndSetNext(pred, predNext, null);//将当前节点的下一个节点置为空,因为当前节点是最后一个节点没有next指针
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&//前驱节点不等于头结点
((ws = pred.waitStatus) == Node.SIGNAL ||//前驱节点的状态不等于SIGNAL
(ws 0) {//下一个节点等于空或者下一个节点是取消节点
s = null;//将s置为空
for (Node t = tail; t != null && t != node; t = t.prev)//从尾结点遍历找到一个不是取消状态的节点
if (t.waitStatus
shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获取上一个节点的等待状态
if (ws == Node.SIGNAL)//如果状态为SIGNAL,代表后续节点有节点可以唤醒,可以安心阻塞去
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//如果当前状态大于0,代表节点为CANCELLED状态
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;//从尾节点开始遍历,找到下一个状态不是CANCELLED的节点。将取消节点断链移除
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//这里需要注意ws>0时,已经找到了一个不是取消状态的前驱节点。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将找到的不是CANCELLED节点的前驱节点,将其等待状态置为SIGNAL
}
return false;
}
cancelAcquire方法
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)//当前节点为空直接返回
return;
node.thread = null;//要取消了将当前节点的线程置为空
// Skip cancelled predecessors
Node pred = node.prev;//获取到当前节点的前驱节点
while (pred.waitStatus > 0)//如果当前节点的前驱节点的状态大于0,代表是取消状态,一直找到不是取消状态的节点
node.prev = pred = pred.prev;
Node predNext = pred.next;//将当前要取消的节点断链
node.waitStatus = Node.CANCELLED;//将当前节点的等待状态置为CANCELLED
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {//如果当前节点是尾结点,将尾结点替换为浅语节点
compareAndSetNext(pred, predNext, null);//将当前节点的下一个节点置为空,因为当前节点是最后一个节点没有next指针
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&//前驱节点不等于头结点
((ws = pred.waitStatus) == Node.SIGNAL ||//前驱节点的状态不等于SIGNAL
(ws
unparkSuccessor方法
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;//获取当前节点状态
if (ws 0) {//下一个节点等于空或者下一个节点是取消节点
s = null;//将s置为空
for (Node t = tail; t != null && t != node; t = t.prev)//从尾结点遍历找到一个不是取消状态的节点
if (t.waitStatus
release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {//子类实现如何释放锁
Node h = head;//获取到头结点
if (h != null && h.waitStatus != 0)//获取到头结点,如果头结点不为空,等待状态不为0,唤醒后续节点
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;//获取节点的等待状态
if (ws 0) {//如果节点是空,或者是取消状态的节点,就找到一个非取消状态的节点,将取消状态的节点断链后由垃圾回收器进行回收
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus
acquireShared方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg)
doAcquireShared方法
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//将共享节点也即读线程入队并返回
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//找到节点的前驱节点
if (p == head) {//如果前驱节点等于头结点
int r = tryAcquireShared(arg);//尝试获取共享锁数量
if (r >= 0) {//如果锁的数量大于0,表示还有多余的共享锁。这里等于0也需要进一步判断。由于如果当执行到这里时,有另外的线程释放了共享锁,如果不进行判断,将会导致释放锁的线程没办法唤醒其他线程。所以这里会伪唤醒一个节点,唤醒的节点后续如果没有锁释放,依旧阻塞在当前parkAndCheckInterrupt方法中
setHeadAndPropagate(node, r);//将当前节点的等待状态设置为Propagate。
p.next = null; // help GC
if (interrupted)//判断是会否中断过
selfInterrupt();//设置中断标识位
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&//判断是否应该阻塞等待
parkAndCheckInterrupt方法中())//阻塞并检查中断标识
interrupted = true;//重置中断标识位
}
} finally {
if (failed)//如果失败
cancelAcquire(node);//取消节点
}
}
setHeadAndPropagate方法
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);//将当前节点置为头结点
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 //可获取的共享锁也即读锁的数量,对于ReentrantReadWriteLock而言,永远都是1,所以会继续唤醒下一个读线程
|| h == null //如果旧的头结点为空
|| h.waitStatus
releaseShared方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//子类实现释放锁
doReleaseShared();//唤醒后续线程
return true;//释放成功
}
return false;//释放是吧
}
doReleaseShared方法
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;//获取到当前头结点
if (h != null && h != tail) {//如果头结点不为空并且不等于尾结点
int ws = h.waitStatus;//获取当前节点的等待状态
if (ws == Node.SIGNAL) {//如果状态为SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//cas将SIGNAL状态置为0。SIGNAL标识后续有线程需要唤醒
continue; // loop to recheck cases
unparkSuccessor(h);//唤醒后续线程
}
else if (ws == 0 &&//如果当前状态为0。表示有线程将其置为0
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//cas将0状态置为PROPAGATE。在多个共享锁同时释放时,方便继续进行读传播,唤醒后续节点
continue; // loop on failed CAS
}
if (h == head)//如果头结点没有改变,证明没有必要继续循环等待了,直接退出吧,如果头结点放生变化,可能有其他线程释放了锁。
break;
}
}
await()
public final void await() throws InterruptedException {
if (Thread.interrupted())//线程是否发生中断,是,就抛出中断异常
throw new InterruptedException();
Node node = addConditionWaiter();//加入条件等待队列
int savedState = fullyRelease(node);//释放锁,并返回。因为当前线程需要等待
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//判断是否在竞争队列中。AQS分为两个队列一个是竞争队列,等待调度执行,一个是等待队列等待在ConditionObject上。
LockSupport.park(this);//阻塞等待
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//重新去获取锁并判断当前中断模式不是THROW_IE
interruptMode = REINTERRUPT;//将中断模式置为REINTERRUPT
if (node.nextWaiter != null) // clean up if cancelled如果当前节点的下一个节点不为空
unlinkCancelledWaiters();//清除等待队列中已经取消的节点
if (interruptMode != 0)//如果当前中断模式不等于0
reportInterruptAfterWait(interruptMode);
}
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)//如果是THROW_IE直接抛出异常
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)//如果是REINTERRUPT
selfInterrupt();//重置中断标识位
}
addConditionWaiter方法
private Node addConditionWaiter() {
Node t = lastWaiter;//获取到最后一个节点
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {//最后一个节点不等于空,并且等待状态不等于CONDITION
unlinkCancelledWaiters();//将取消节点断链,标准的链表操作
t = lastWaiter;//获取到最后一个有效的节点
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);//将当前节点封装成node
if (t == null)//如果最后一个节点为空,表示当前节点是第一个入队的节点
firstWaiter = node;
else
t.nextWaiter = node;//否则将当前node挂在链表末尾
lastWaiter = node;//设置最后节点的指针指向当前node
return node;
}
fullyRelease方法
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//获取当前state状态
if (release(savedState)) {//释放锁尝试
failed = false;
return savedState;//返回
} else {
throw new IllegalMonitorStateException();//抛出释放锁异常
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;//如果失败将节点置为取消状态
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放锁,在CyclciBarrier中由于线程需要去阻塞,所以需要将锁释放,后续重新拿锁
Node h = head;
if (h != null && h.waitStatus != 0)//从头结点开始唤醒
unparkSuccessor(h);
return true;
}
return false;
}
isOnSyncQueue方法
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)//如果当前节点是Condition或者node.pre节点为空,标识不在竞争队列中,返回faslse
return false;
if (node.next != null) // If has successor, it must be on queue 表示在竞争队列中
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);//从竞争队列的尾结点开始找当前node,找到就返回true,否则为false
}
private boolean findNodeFromTail(Node node) {
Node t = tail;//获取到尾结点
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
findNodeFromTail方法
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?//判断当前是否中断过
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) ://如果移动到竞争队列中并入队成功,返回THROW_IE,否则返回REINTERRUPT
0;//没有中断过直接返回0
}
//走到这里表示条件队列的条件满足,可以将节点移动到竞争队列中执行
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {//尝试将当前为Condition的节点置为0,并移动到竞争队列中
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))//如果不在竞争队列中返回false
Thread.yield();
return false;
}
signalAll方法
public final void signalAll() {
if (!isHeldExclusively())//是不是持有独占锁
throw new IllegalMonitorStateException();
Node first = firstWaiter;//获取等待队列的第一个节点
if (first != null)//如果节点不为空
doSignalAll(first);//唤醒所有线程
}
//从头指针一直遍历等待队列,将其移动到竞争队列中
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);//
first = next;
} while (first != null);
}
transferForSignal方法
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//cas自旋将其等待状态改为0
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);//将其放入竞争队列
int ws = p.waitStatus;//获取节点的等待状态
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//如果节点是取消状态或者cas将其置为signal失败,唤醒当前线程,让他自己处理,后续在竞争队列中会自动移除取消节点
LockSupport.unpark(node.thread);
return true;
}
总结:AQS提供了统一的模板,对于如何入队出队以及线程的唤醒都由AQS提供默认的实现,只需要子类实现自己上锁和解锁的逻辑。
3.Semaphore
基本使用
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
//Semaphore s = new Semaphore(2);
Semaphore s = new Semaphore(2, true);
//允许一个线程同时执行
//Semaphore s = new Semaphore(1);
new Thread(() -> {
try {
s.acquire();
System.out.println("T1 running...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
new Thread(() -> {
try {
s.acquire();
System.out.println("T2 running...");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
}
}
Sync类
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();//获取state中的可用信号量
int remaining = available - acquires;//减1
if (remaining current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))//cas尝试减少信号量
return;
}
}
//清空信号数量
final int drainPermits() {
for (;;) {
int current = getState();//获取当前state状态
if (current == 0 || compareAndSetState(current, 0))//当前信号为0 或者将state置为0也即将信号数量置为0
return current;
}
}
}
FairSync与NonfairSync的类实现
//公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())//队列中是否有线程在排队
return -1;//获取失败
int available = getState();//可用的信号量
int remaining = available - acquires;//减去当前获取的数量
if (remaining
acquire方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);//请查看父类实现,与acquireShared一致,不过加了一场处理
}
release方法:
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//Semaphore的类实现锁获取的方法。
doReleaseShared();//与AQS中一致,不过多赘述
return true;
}
return false;
}
4.留言
到了这里,其实AQS的源码基本已经覆盖了,对于AQS的源码也应该有了清楚的认知。总结就是:一个volatile 的state变量,两个等待队列(竞争队列,条件队列),通过cas的方式保证单变量的原子性。后续将会对Exchanger以及Phaser进行源码解析,到此基本AQS已经到了一个段落了。后续观看源码时,请注意多考虑一下多线程并发时可能出现的情况,去理解doug lea写代码的思路。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 如何给一本书写推荐语,先别急着说“没用的知识又增加了”……
先感谢电子社给发了个奖,还好我已经过40岁了,不然“感谢您40年以来……”,还真的有点怪怪的,:) 在这群人中间,我还算年轻,继续努力。 可能因为出版了几本书,卖了大几十万本,还算卖得不错的关系,这几年,找我写推荐语的作者挺多,频率超过每月一本,也算是强制看书…