Java并发编程 --- AQS再续前缘
在同步机制中,我们介绍了AQS与ReentrantLock。当然使用AQS进行同步的也不止ReentrantLock,所以我们接下来去看看其他用AQS做同步的类。
ReentrantReadWriteLock
概述
ReentrantReadWriteLock的读锁是共享锁,写锁是独占锁。
使用时,读-读支持并发,读-写互斥,写-写互斥。
重入时升级不支持:如果一个线程获取了读锁,然后想再获取写锁,那是不被允许的。
重入时降级支持:如果一个线程获取了写锁,允许再持有读锁。
ReadLock
lock()
public void lock() {
sync.acquireShared(1);
}
ReadLock的lock调用了同步器的acquireShared(1)方法。
public final void acquireShared(int arg) {
// tryAcquireShared 返回负数, 表示获取读锁失败
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
进入同步器的方法,其中tryAcquireShared(arg)是尝试去获取锁,其返回值-1表示失败,0表示成功,但后继节点不会继续唤醒。正数表示成功,而且数值是后面还有几个后继节点需要唤醒,读写锁返回 1。
protected final int tryAcquireShared(int unused) {
//获得当前线程
Thread current = Thread.currentThread();
int c = getState();
// exclusiveCount(c) 代表低 16 位, 写锁的 state,成立说明有线程持有写锁
// 写锁的持有者不是当前线程,则获取读锁失败,【写锁允许降级】
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
// 读锁是否应该阻塞
if (!readerShouldBlock() &&
r < MAX_COUNT &&
// 尝试增加读锁计数
compareAndSetState(c, c + SHARED_UNIT)) {
//加锁之前发现读锁为0 说明没人用读锁
if (r == 0) {
//指明当前线程为第一个Reader
firstReader = current;
//firstReaderHoldCount为第一个Reader的入锁次数
firstReaderHoldCount = 1;
//发生的读锁的重入
} else if (firstReader == current) {
//第一个Reader的入锁次数+1
firstReaderHoldCount++;
} else {
// cachedHoldCounter 设置为当前线程的 holdCounter 对象,即最后一个获取读锁的线程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
//设置最后一个获取读锁的线程
cachedHoldCounter = rh = readHolds.get();
// 还没重入
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
// 逻辑到这应该阻塞,或者 cas 加锁失败
// 会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
// 当前读锁线程持有的读锁次数对象
HoldCounter rh = null;
for (;;) {
int c = getState();
// 说明有线程持有写锁 持有写锁的线程可以进一步获取读锁
if (exclusiveCount(c) != 0) {
//如果持有写锁的不是当前线程
if (getExclusiveOwnerThread() != current)
return -1;
//读锁是否应该阻塞
} else if (readerShouldBlock()) {
//第一个持有读锁线程是否是当前线程,条件成立说明当前线程是 firstReader,当前锁是读忙碌状态,而且当前线程也是读锁重入
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
// 最后一个读锁的 HoldCounter
rh = cachedHoldCounter;
// 说明当前线程也不是最后一个读锁
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
//溢出会抛出异常
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// CAS让读数据+1
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
//指明当前线程为第一个Reader
firstReader = current;
//初始化第一个Reader的入锁次数
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//第一个Reader的入锁次数+1
firstReaderHoldCount++;
} else {
//cachedHoldCounter为成功获取 readLock 的最后一个线程的保持计数
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
//更新计数器
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
上述为tryAcquireShared的业务代码,需要判断的有:
1、是否为锁重入,为锁重入可以获取读锁。(因为不管是当前线程持有写锁还是读锁,都能再次拿到读锁)
2、是否有人获取了写锁,若获取写锁的线程可以成功获取读锁。当遇到阻塞,或者 cas 加锁失败就会执行fullTryAcquireShared的业务逻辑了。
private void doAcquireShared(int arg) {
//将当前节点添加到Node队列,同时设置Node的状态为SHARED
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) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 是否在获取读锁失败时阻塞
// shouldParkAfterFailedAcquire(p, node)将node的前驱节点的WaitStatus设置为-1,代表它需要唤醒它的后继节点。
if (shouldParkAfterFailedAcquire(p, node) &&
//parkAndCheckInterrupt()将当前线程park阻塞住
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
获取读锁失败,进入 sync.doAcquireShared(1) 流程开始阻塞,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时线程 仍处于活跃状态。然后进入for()循环块中,再次使用tryAcquireShared(arg)尝试获取锁,若锁获取失败,则调用shouldParkAfterFailedAcquire(p, node)将node的前驱节点的WaitStatus设置为-1,代表它需要唤醒它的后继节点。
当有人唤醒阻塞线程会再进入for一次,再次使用tryAcquireShared(arg)尝试获取锁,若还是没有获取成功,parkAndCheckInterrupt()将当前线程park阻塞住。
unlock()
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//尝试释放锁
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
unlock的核心逻辑为同步器的releaseShared方法。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//判断第一个获取读锁的是不是当前线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//获取最后一个获取写锁的人
HoldCounter rh = cachedHoldCounter;
//rh == null 表示无人获取
//rh.tid != getThreadId(current) 表示当前线程不是获取写锁的线程
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
//用cas直到把读锁的状态量置为0
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
如果释放锁成功之后,那就执行doReleaseShared();去释放它的后继节点,如果后继节点也为Shared则能成功释放。
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;
//判断waitStatus是否为-1 若为-1 则证明要唤醒后继节点
if (ws == Node.SIGNAL) {
//使用cas将waitStatus从-1变成0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//执行到这就要唤醒后继节点了 具体操作为唤醒节点,并将该节点替换头节点。
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
//执行unpark后继节点
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.
*/
//获取当前节点的waitstatus
int ws = node.waitStatus;
//若小于0 则说明有职责唤醒后继节点
if (ws < 0)
//CAS 将waitstatus置为0
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//获取下一个节点
Node s = node.next;
//若下一个节点不存在或者被取消 则需要找一个重新唤醒的对象
if (s == null || s.waitStatus > 0) {
s = null;
//从后往前遍历
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//s不为null 开始唤醒
if (s != null)
//本质由Unsafe提供unpark方法
LockSupport.unpark(s.thread);
}
WriteLock
lock()
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
//tryAcquire(arg)是尝试去获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
lock()主要使用了同步器的acquire(1)方法。
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
//表示已经获取的写锁或者读锁了
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//w == 0表明不是获取的写锁,那肯定是获取了读锁,获取读锁不能再获取写锁(无法锁升级)
//w != 0说明该线程获取的是写锁,但锁的拥有者不是当前线程,则获取写锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//防止溢出
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//锁重入
setState(c + acquires);
return true;
}
// c == 0,说明没有任何锁,判断写锁是否该阻塞,是 false 就尝试获取锁,失败返回 false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置锁主人
setExclusiveOwnerThread(current);
return true;
}
上述是tryAcquire方法,它讨论了两种情况:
1、c≠0,那么就有可能有线程获取了读锁或者写锁,若有线程获取了读锁,那其他线程则无法获取写锁(读-写互斥);若有线程获取了写锁,那么要看写锁的拥有者是否是当前线程,若是,则相当于锁重入,若不是,则无法获取写锁。
2、c=0,说明没有任何锁,判断写锁是否该阻塞,是 false 就尝试获取锁,失败返回 false
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)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued是将当前节点加到Node队列中,且Node的状态为EXCLUSIVE。
unlock()
public void unlock() {
// 释放锁
sync.release(1);
}
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 头节点不为空并且不是等待状态不是 0,唤醒后继的非取消节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 因为可重入的原因, 写锁计数为 0, 才算释放成功
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
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)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
//下一个节点为空或者节点取消
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
StampedLock
概述
StampedLock与ReentrantLock不同的点在于,StampedLock提供了乐观读机制。
乐观读机制
StampedLock的乐观读机制,类似于无锁操作,完全不会阻塞写线程获取写锁。
作用:可以缓解读多写少时写线程的"饥饿"现象。
适用场景
适用于读多写少的场景,可以避免写线程饥饿。
DEMO & TIPS
由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时需要使用以下流程:
1、先使用tryOptimisticRead获取时间戳(如果有写锁,直接返回0)
2、使用validate去检验锁状态,判断数据是否一致
class StampedLockTest{
private int data;
private final StampedLock lock = new StampedLock();
public StampedLockTest(int data){
this.data = data;
}
/**
* 传入的readTime为模拟获取乐观读时间戳后所需要的执行业务时间。
*/
public int read(int readTime){
//乐观读
long stamp = lock.tryOptimisticRead();
System.out.println("stamp = " + stamp);
try {
Thread.sleep(readTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//检验戳
if(lock.validate(stamp)){
System.out.println("乐观读成功");
return data;
}
System.out.println("乐观读失败,进行加锁");
try {
stamp = lock.readLock();
System.out.println("读取结果结束");
return data;
}finally {
lock.unlockRead(stamp);
}
}
public void write(int data){
//获取写锁
long stamp = lock.writeLock();
System.out.println("stamp = " + stamp);
try {
this.data = data;
System.out.println("修改数据成功");
}finally {
lock.unlockWrite(stamp);
}
}
}
总结
乐观读操作的步骤如下:
①先使用tryOptimisticRead()获取时间戳stamp
②使用validate(long stamp)去验证时间戳是否失效。
失效场景:有其他线程获取了写锁
③若失效,则进一步去获取读锁。
原理
锁状态
StampedLock提供了写锁、悲观读锁、乐观锁三种模式。
悲观读锁:state的前7位(0-7位)表示获取读锁的线程数。如果超过0-7位最大容量 255,则使用一个名为 readerOverflow 的 int 整型保存超出数。
所以在加写锁时,只需要 state & 255,若结果大于0,则表明有线程数持有读锁。结果为0,则可以直接加写锁。(当然还需要CAS竞争写锁)
tryOptimisticRead()
tips:
①private static final long WBIT = 1L << LG_READERS = 128
②private static final long RBITS = WBIT - 1L = 127
③private static final long SBITS = ~RBITS = -128
public long tryOptimisticRead() {
long s;
// 若已有写锁获取 直接返回0L
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}
validate(long stamp)
public boolean validate(long stamp) {
//内存屏障
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}
使用了Unsafe提供的loadFence(),主要起到了内存屏障的作用,它确保在该方法调用之后的读操作不会被重排序到该方法调用之前。这有助于保证多线程环境下,对共享变量的读取操作能够看到其他线程对该变量的最新写入结果。
ReadLock
lock()
public void lock() {
readLock();
}
public long readLock() {
long s = state, next;
// 当whead == wtail时 表示等待队列现在没有人 可获取锁
return ((whead == wtail && (s & ABITS) < RFULL &&
U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
next : acquireRead(false, 0L));
}
unlock()
public void unlock() { unstampedUnlockRead(); }
final void unstampedUnlockRead() {
for (;;) {
long s, m; WNode h;
if ((m = (s = state) & ABITS) == 0L || m >= WBIT)
throw new IllegalMonitorStateException();
else if (m < RFULL) {
// CAS将state--
if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) {
if (m == RUNIT && (h = whead) != null && h.status != 0)
//唤醒 h 的后继者(通常为 whead)。这通常只是 h.next,但如果 next 指针滞后,则可能需要从 wtail 遍历。
release(h);
break;
}
}
// 尝试递减 readerOverflow
else if (tryDecReaderOverflow(s) != 0L)
break;
}
}
WriteLock
lock()
public void lock() { writeLock(); }
public long writeLock() {
long s, next;
// 先判断锁是否被读锁阻塞
return ((((s = state) & ABITS) == 0L &&
// 使用CAS进行state的写资源++
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : acquireWrite(false, 0L));
}
Semaphore
概况
Semaphore又名为信号量。使用时,需要指定该信号量有多少通行证(permits),通行证表明了有多少资源数可以被线程获取。
原理
acquire()
该方法的目的是为了获取通行证,本质是通过CAS获取。
//默认获取一个通行证
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//获取指定的通行证
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//线程打断直接停止
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取通行证(核心) 大于等于0表示获取成功 小于0表示获取失败
if (tryAcquireShared(arg) < 0)
//获取通行证失败 需要进行相应的阻塞
doAcquireSharedInterruptibly(arg);
}
//核心(公平版本)
protected int tryAcquireShared(int acquires) {
for (;;) {
//公平版本比非公平版本多了这个判断条件
//当发现队列中有人时就直接默认获取失败
if (hasQueuedPredecessors())
return -1;
//获取state
int available = getState();
//预期结果
int remaining = available - acquires;
//如果预期结果小于0 那么肯定就是没有资源了 所以直接退出
if (remaining < 0 ||
// 通过CAS更新资源(由Unsafe鼎力支持)
compareAndSetState(available, remaining))
return remaining;
}
}
//CAS
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// 作用:①如果是队列中的头节点,会做最后的挣扎 ②如果没有会进行park ③允许等待过程中打断
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//为当前的线程创建节点并将其放入指定的队列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//返回node的前驱节点
final Node p = node.predecessor();
//如果相等 表示它是等待队列中第一个元素
if (p == head) {
//继续进行CAS资源的获取 (垂死挣扎)
int r = tryAcquireShared(arg);
//表示获取成功
if (r >= 0) {
//设置队列头,并检查后续任务是否可以在共享模式下等待 (如下细讲)
setHeadAndPropagate(node, r);
//将最开始的队列头置为null 便于GC进行垃圾回收 不然会被强制绑定 无法GC
p.next = null; // help GC
failed = false;
return;
}
}
//如果执行到这里 就意味着 线程需要进行Park操作了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
//执行到这里说明被打断
if (failed)
//取消请求获取通行证资源
cancelAcquire(node);
}
}
//目标:①更换队列头 ②在有资源的前提下去唤醒后继共享节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//唤醒后继共享节点
doReleaseShared();
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
// 如果head为null 或者 head == tail(表明队列中无人) 则直接结束
if (h != null && h != tail) {
int ws = h.waitStatus;
// ws==Node.SIGNAL表示当前节点有义务唤醒后继节点
if (ws == Node.SIGNAL) {
// 用CAS将waitstatus变为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒后继节点操作
unparkSuccessor(h);
}
//无条件传播
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
//唤醒后继节点逻辑
private void unparkSuccessor(Node node) {
//再上一层保险 保证它肯定waitstatus变为0再执行唤醒后继节点操作
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//满足条件即说明 node.next是一个已经取消或不存在的点 所以需要我们重新去查找
if (s == null || s.waitStatus > 0) {
s = null;
//从后往前 去查找非取消节点 (为什么这么做,楼下细说)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
在unparkSuccessor中,为什么从后往前遍历寻找未被取消的节点呢呢?
在我们阅读发现一个有趣的现象,那就是为什么要从后往前遍历。
首先先看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
Node pred = tail;
if (pred != null) {
//绑定老尾部节点
node.prev = pred;
//CAS去设置自己为尾部节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
以图绘制发来说明情况
存在情况如下:如果刚好没执行pred.next = node;然后此时需要从前往后找去唤醒,那么我们其实的唤醒的是新节点,但是由于他们还没建立正向通道,所以遍历不到新节点。
当然,还有另外一个原因,在节点取消设置为CANCEL时,会先断掉向前的指针,造成遍历不到最新的节点。
release()
其实严格来说,他的主要作用是补充通行证,他不需要你持有通行证才能补充,只要你想随时补充。(Tips:会有一个溢出的问题)
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//执行补充许可证的主要作用(溢出会抛出异常)
if (tryReleaseShared(arg)) {
//唤醒等待链中的线程
doReleaseShared();
return true;
}
return false;
}
//执行补充许可证的操作(用CAS保证数据安全)
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去补充通行证,保证线程安全
if (compareAndSetState(current, next))
return true;
}
}
//执行唤醒后继节点的操作
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//判断ws是否为-1 如果是-1就要唤醒后继节点
if (ws == Node.SIGNAL) {
//用cas将waitStatus从-1置为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒后继节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
CountDownLatch
概述
又名为计数器,用来进行线程同步,等待所有线程完成。
方法
public CountDownLatch(int count) : 初始化需要coutDown几次才能唤醒所有线程。
public void await() : 让当前线程等待,必须 down 完初始化的数字才可以被唤醒,否则进入无限等待
public void countDown() : 计数器进行减 1(down 1)
DEMO
public class test4 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
ExecutorService service = Executors.newFixedThreadPool(10);
Random random = new Random();
String[] str = new String[10];
for (int i = 0; i < 10; i++) {
int k = i;
service.execute(() -> {
for (int j = 0; j <= 100; j++) {
str[k] = j+"%";
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.print("\r"+ Arrays.toString(str));
}
//减一
countDownLatch.countDown();
});
}
//等待count减到0
countDownLatch.await();
System.out.println("\n"+"等待结束");
}
}
原理
await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 提供可打断机制
if (Thread.interrupted())
throw new InterruptedException();
// 尝试去释放
if (tryAcquireShared(arg) < 0)
// 小于0,如要加入等待队列中
doAcquireSharedInterruptibly(arg);
}
// 释放机制
protected int tryAcquireShared(int acquires) {
// 若state为0,表示可释放;若不为0,表示不可释放。
return (getState() == 0) ? 1 : -1;
}
//
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
countDown()
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
// 尝试state-1
if (tryReleaseShared(arg)) {
// 看唤醒后续的节点去减1
doReleaseShared();
return true;
}
return false;
}
// CAS去循环尝试将state-1
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
//唤醒后继节点
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) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构