1、AQS
1.1、概述
全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,其具有特点
- 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
- getState - 获取 state 状态
- setState - 设置 state 状态
- compareAndSetState - CAS 机制设置 state 状态
- 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
- 提供了基于 FIFO 先进先出的等待队列,类似于 Monitor 的 EntryList
- 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet,还有 ReentrantLock 的 Condition
其子类需要自己选择实现下面几个方法中的一部分,因为 AQS 对这几个方法都默认实现了一个抛出 UnsupportedOperationException
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
1.2、自定义不可重入锁
class MyLock implements Lock {
static class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public ConditionObject newCondition() {
return new ConditionObject();
}
}
private final MySync sync = new MySync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
测试
MyLock myLock = new MyLock();
new Thread(() -> {
myLock.lock();
log.info("lock...{}", LocalDateTime.now());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myLock.unlock();
log.info("unlock...{}", LocalDateTime.now());
}).start();
new Thread(() -> {
myLock.lock();
log.info("lock...{}", LocalDateTime.now());
myLock.unlock();
log.info("unlock...{}", LocalDateTime.now());
}).start();
重复加锁测试
MyLock myLock = new MyLock();
new Thread(() -> {
myLock.lock();
log.info("lock...{}", LocalDateTime.now());
myLock.lock();
log.info("lock...{}", LocalDateTime.now());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myLock.unlock();
log.info("unlock...{}", LocalDateTime.now());
}).start();
可以看到是不可重入的,线程就被阻塞住了,只有解开了才能重新加
2、ReentrantLock
2.1、非公平锁
2.1.1、加锁解锁流程
先从构造器开始看,默认为非公平锁实现(谁先抢到谁获得锁)
NonfairSync 继承自 Sync,Sync 继承自 AQS
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
- 没有竞争的时候,exclusiveOwnerThread 将被标记为 t1
2.1.1.1、加锁失败
- 此时 t2 来了,开始 CAS 希望将 state 从 0 改为 1,但是失败了,进入 acquire 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
-
可以看到 acquire 又调用了自身的 tryAcquire 方法,但是此时肯定是失败的,就会进入 acquireQueued 方法,这个方法会先调用 addWaiter 创建一个 Node 节点对象,然后 acquireQueued 加入等待队列中去
-
Node 队列为一个双向链表,且每个节点默认正常状态 waitStatus 为 0,对于此双向链表的头部(head 指向)节点用来占位,不关联线程,称其为 Dummy(哑元)或哨兵
- 进入 acquireQueued 逻辑方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//拿到当前新创建的Node的前驱Node,现在也就是那个哨兵
final Node p = node.predecessor();
//如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果失败了,先判断是否需要park,如果需要park,则执行parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
-
可以看到,代码本身会进行一个死循环,不断的尝试获得锁,失败后会进入 park 阻塞
-
此时进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false,其中 SIGNAL = -1 的含义便是,它有责任唤醒其后继节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//Node.SIGNAL = -1
if (ws == Node.SIGNAL)
/*
* 该节点已经设置了状态,要求释放以发出信号,因此它可以安全地park。
*/
return true;
if (ws > 0) {
/*
* 前驱节点被取消。跳过前驱并指示重试。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//此时 ws = 0
/*
* waitStatus 必须为 0 或 PROPAGATE = -3。表示我们需要一个信号,但不要park。呼叫者需要重试以确保在park前无法获取锁。
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 再次进入循环,再次尝试获取锁,失败,再次进入 shouldParkAfterFailedAcquire ,此时前驱节点已经是 -1 了,直接返回 true,接着调用 parkAndCheckInterrupt,开始阻塞
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 此时再来一个线程,尝试获得锁,失败,又创建一个 Node 节点进入 acquireQueued 方法,现在其前驱节点不是 head 直接进入 shouldParkAfterFailedAcquire 方法
- 现在其前驱节点为 t2 状态还是 0 ,所以将其设置为 -1,接着本 Node 再次 park
- 再次来一个,将会变为这个样子
2.1.1.2、解锁竞争
- 此时 t1 释放锁,进入 unlock->release->tryRelease 方法,最终就是将状态设置为 0
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//头节点已经被创建,且其后继有节点
if (h != null && h.waitStatus != 0)
//唤醒其后继节点
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
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)
//改为 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;
//从最后开始往前找,直到找到 node 节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//将node节点对应的线程唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
- 队列的第一个节点线程,其本身还阻塞在 parkAndCheckInterrupt 方法,唤醒后,就开始重新循环,获得锁
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;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 如果此时在唤醒 t2 获得锁的过程中,又来了一个线程,其不在阻塞队列中,它直接先抢到了锁,那么 t2 只能重新进入 park 阻塞(非公平的体现)
2.2、可重入原理
直接看代码注释
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
//来自父类 Sync
return nonfairTryAcquire(acquires);
}
}
//父类Sync中的方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//同一个线程来了,肯定是获取不到锁的,如果加锁的线程和当前线程相同,表示发生了锁重入,则进入如下分支
else if (current == getExclusiveOwnerThread()) {
//state++
int nextc = c + acquires;
if (nextc < 0) // 溢出(谁入这么多啊)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//释放锁的方法,也在父类Sync中
protected final boolean tryRelease(int releases) {
//释放锁,因为可重入,所以不能直接置为0
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果已经减为0了,才会真正释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
//只有释放完了才能解锁
return free;
}
2.3、可打断原理
因为 ReentrantLock 支持设置为不可打断模式 (lock 为不可打断,lockInterruptibly 为可打断模式)
2.3.1、不可打断模式
如果被打断了,就会调用 interrupted 方法,返回是否打断,然后还会重置打断标记
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
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())
//被打断了,就会将表示设置为true,此标记只有在自己真正获得了锁后才会用到
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果是被打断过,就进入此方法
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
可以发现在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
2.3.2、可打断模式
当调用 lockInterruptibly 方法后:
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
//如果没有获得锁,进入此方法
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//同样的地方,只不过如果被打断了,直接就抛出异常,停止 AQS 队列中继续等了,直接跳出死循环
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.4、公平锁
对于非公平锁,调用 tryAcquire 后,直接就开始抢锁了,没有管 AQS 队列中的其他线程
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果还没有占用
if (c == 0) {
//在占用锁之前,先判断是否有前驱节点,没有才会去竞争
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
//尾巴
Node t = tail; // Read fields in reverse initialization order
//头
Node h = head;
Node s;
//头不等于尾,证明队列中有Node
return h != t &&
//表示队列中还没有老二
((s = h.next) == null ||
//或者队列中老二线程不是此线程
s.thread != Thread.currentThread());
}
2.5、条件变量ConditionObject
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
2.5.1、await
调用 await 便会调用 addConditionWaiter 创建一个新的 Node 节点
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//创建一个新的 Node
Node node = addConditionWaiter();
//为什么叫fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//Node.CONDITION = -2
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
//尾插法
t.nextWaiter = node;
lastWaiter = node;
return node;
}
- 一开始 t1 持有锁,然后调用 await 方法,进入 addConditionWaiter 方法,创建新的 Node ,状态为 -2,并将其关联到 t1,加入等待队列尾部
- 然后调用 fullyRelease 方法,为什么叫 fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
final int fullyRelease(Node node) {
boolean failed = true;
try {
//拿到总state
int savedState = getState();
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)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒阻塞队列中的其他节点
unparkSuccessor(h);
return true;
}
return false;
}
- 如果没有其他线程竞争锁,那么应该是 t2 线程获得锁
2.5.2、signal
现在,t2 要唤醒刚刚的 t2,会将当前等待队列的头节点转移到 AQS 等待队列中,如果转移失败,证明当前节点已经被取消执行,会继续找下一个节点,如果转移成功,就会解锁,让等待队列的其余节点开始竞争锁
public final void signal() {
//判断当前线程是否持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//找到等待队列的头节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//将头节点去掉
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//会将当前头节点转移到竞争锁的队列中,如果转移失败,就会继续找下一个节点
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
* 如果不能被转移,那就证明这个任务已经被取消了,就没有必要转移了
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 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;
//设置前一个节点状态为-1
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//执行解锁操作
LockSupport.unpark(node.thread);
return true;
}
3、读写锁
3.1、ReentrantReadWriteLock使用
当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select … from … lock in share mode ,简单编写一个测试类
@Slf4j
class DataContainer {
private Object data;
private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock r = rw.readLock();
private final ReentrantReadWriteLock.WriteLock w = rw.writeLock();
@SneakyThrows
public Object read() {
log.info("获取读锁...{}", LocalDateTime.now());
r.lock();
try {
log.info("读取 {}", LocalDateTime.now());
TimeUnit.SECONDS.sleep(1);
return data;
} finally {
log.info("释放读锁...{}", LocalDateTime.now());
r.unlock();
}
}
@SneakyThrows
public void write() {
log.info("获取写锁...{}", LocalDateTime.now());
w.lock();
try {
log.info("写入 {}", LocalDateTime.now());
TimeUnit.SECONDS.sleep(1);
} finally {
log.info("释放写锁...{}", LocalDateTime.now());
w.unlock();
}
}
}
- 测试 读-读
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::read, "t1").start();
new Thread(dataContainer::read, "t2").start();
可以发现,读读操作是畅通无阻的
- 测试 读-写
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::read, "t1").start();
new Thread(dataContainer::write, "t2").start();
可以发现,读写操作,是互斥的
- 测试 写-写
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::write, "t1").start();
new Thread(dataContainer::write, "t2").start();
写写,也是互斥的
注意:
- 读锁不支持条件变量
- 重入时升级不支持(即持有读锁的情况下去获取写锁,会导致获取写锁永久等待),因为读锁不能保证会有其他的读锁✌️
- 但是支持降级(也就是持有写锁的时候,再去获取读锁)
3.2、读写锁原理
读写锁用的是同一个 Sync 同步器,因此等待队列、state 等也是同一个
-
t1 写,t2 读
-
t1 成功上写锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位
//写锁的方法
public void lock() {
sync.acquireShared(1);
}
//这个方法熟,就是之前看的 AQS 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//ReentrantReadWriteLock重写的方法
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);
//不为0,可能是其他线程加了读锁,也有可能是加了写锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//如果写锁等于0,那表示之前已经加了读锁,如果家的就是写锁,再判断是不是自己加的(重入了)
if (w == 0 || current != getExclusiveOwnerThread())
//读写互斥,直接返回false
return false;
//如果写锁加上1超过最大值了(16 位 65535),则重入次数太多了
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//写锁基础上+1,重入了
setState(c + acquires);
return true;
}
//如果没有加锁,那我就来,首先判断当前线程是否要阻塞,如果是非公平锁,总是返回false,就需要等待
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置自己为Owner线程
setExclusiveOwnerThread(current);
return true;
}
- t2 这时候来了,执行读锁的 lock 方法,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败
- -1 表示失败
- 0 表示成功,但后继节点不会继续唤醒==(读写锁不涉及)==
- 正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回 1
//读锁重写的方法
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
//检验写锁是否为0
if (exclusiveCount(c) != 0 &&
//前一个判断已经加了写锁了,然后加读锁的就是自己,又来加读锁,应该是允许的,否则就 -1
getExclusiveOwnerThread() != current)
//这里直接就返回 -1
return -1;
//
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
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;
}
return fullTryAcquireShared(current);
}
- 如果加锁失败就会进入 doAcquireShared 这个方法,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式(读锁共享)而非 Node.EXCLUSIVE 模式(写锁独占),注意此时 t2 仍处于活跃状态
//如果加锁失败就会进入这个方法
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) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//当前线程是否需要park,如果park了,就让前驱节点的状态改为-1,有责任唤醒当前线程,然后返回false,进入下一次循环
if (shouldParkAfterFailedAcquire(p, node) &&
//第二次进来后park
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
-
t3 读 t4 写
-
现在这种状态下,假设又来了 t3 加读锁,t4 加写锁,这期间 t1 仍然持有锁,就变成下面的样子
- 现在 t1 准备解锁,执行 unlock -> release -> tryRelease
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
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;
//判断是否重入
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
- 释放锁后,调用 unparkSuccessor 方法开启唤醒流程,这时 t2 恢复运行,从 parkAndCheckInterrupt 处恢复运行,重新执行 for 循环,然后获得锁
- 然后调用 setHeadAndPropagate 方法,此方法将 t2 原本的节点设置为头节点
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) {
//
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 在 setHeadAndPropagate 方法中还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒之(先改为 0 是为了避免其他线程也执行到这里,做相同的操作),此时 t2 和 t3 都处于活跃状态,t3 回来后又执行了 tryAcquireShared 方法,读锁还会加 1
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 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
此时 t3 又执行到了 setHeadAndPropagate,相同的逻辑,t3 节点被置为头节点,其后方的 t4 不再是共享了,所以停止唤醒
-
t2 解锁,t3 解锁
-
t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但此时计数还不为零,t3 再次解锁才能减为 0
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//只有减为0才能全部释放
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) {
//避免其他线程操作
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;
}
}
3.3、StampedLock
该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
- 加解读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
- 加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次戳校验,如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}
编写一个数据容器类,内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
@Slf4j
class DataContainerStamped {
private int data;
private final StampedLock lock = new StampedLock();
public DataContainerStamped(int data) {
this.data = data;
}
@SneakyThrows
public int read(int readTime) {
long stamp = lock.tryOptimisticRead();
log.info("optimistic read locking...{},{}", stamp, LocalDateTime.now());
TimeUnit.SECONDS.sleep(readTime);
if (lock.validate(stamp)) {
log.info("read finish...{}, data:{},{}", stamp, data, LocalDateTime.now());
return data;
}
// 锁升级 - 读锁
log.info("updating to read lock... {},{}", stamp, LocalDateTime.now());
try {
stamp = lock.readLock();
log.info("read lock {},{}", stamp, LocalDateTime.now());
TimeUnit.SECONDS.sleep(readTime);
log.info("read finish...{}, data:{},{}", stamp, data, LocalDateTime.now());
return data;
} finally {
log.info("read unlock {},{}", stamp, LocalDateTime.now());
lock.unlockRead(stamp);
}
}
@SneakyThrows
public void write(int newData) {
long stamp = lock.writeLock();
log.info("write lock {},{}", stamp, LocalDateTime.now());
try {
TimeUnit.SECONDS.sleep(2);
this.data = newData;
} finally {
log.info("write unlock {},{}", stamp, LocalDateTime.now());
lock.unlockWrite(stamp);
}
}
}
读读
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
dataContainer.read(1);
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(500);
new Thread(() -> {
dataContainer.read(0);
}, "t2").start();
读写
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
dataContainer.read(1);
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(500);
new Thread(() -> {
dataContainer.write(100);
}, "t2").start();
注意:StampedLock 不支持条件变量,不支持可重入
4、Semaphore
信号量,用来限制能同时访问共享资源的线程上限。
4.1、基本使用
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
可以看到,如果是调用不带布尔参数的构造方法,默认是一个非公平锁(不管先后,谁抢到谁获得锁),如果带了且为 True ,则创建的就是一个公平锁(严格按照先来后到)
// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 3. 获取许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.info("running...");
TimeUnit.SECONDS.sleep(1);
log.info("end...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4. 释放许可
semaphore.release();
}
}).start();
}
4.2、应用
在学习享元模式的时候,我们使用了 wait notify 实现了一个伪数据库连接池
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
MockPool mockPool = new MockPool(5);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
MockConnection connection = mockPool.getConnection();
System.out.println("拿到连接:" + connection.getConnectionName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("归还连接:" + connection.getConnectionName());
mockPool.free(connection);
}).start();
}
}
}
final class MockPool {
private final MockConnection[] connections;
private final AtomicIntegerArray state;
private static final Integer DEFAULT_POOL_SIZE = 5;
public MockPool() {
connections = new MockConnection[DEFAULT_POOL_SIZE];
state = new AtomicIntegerArray(DEFAULT_POOL_SIZE);
initPool();
}
public MockPool(Integer poolSize) {
connections = new MockConnection[poolSize];
state = new AtomicIntegerArray(poolSize);
initPool();
}
private void initPool() {
for (int i = 0; i < connections.length; i++) {
connections[i] = new MockConnection("连接-" + (i + 1));
}
}
@SneakyThrows
public MockConnection getConnection() {
for (; ; ) {
for (int i = 0; i < connections.length; i++) {
if (state.get(1) == 0) {
if (state.compareAndSet(i, 0, 1)) {
return connections[i];
}
}
}
synchronized (this) {
System.out.println("无可用连接,等待...");
wait();
}
}
}
public void free(MockConnection connection) {
for (int i = 0; i < connections.length; i++) {
if (connections[i] == connection) {
state.set(i, 0);
synchronized (this) {
notifyAll();
}
return;
}
}
System.out.println("要释放的连接不存在");
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class MockConnection {
private String connectionName;
}
wait notify 偏底层了,对于初学者不易掌握,容易出错,所以我们使用 Semaphore 改进
/**
* @author 两米以下皆凡人
*/
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
MockPool mockPool = new MockPool(5);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
MockConnection connection = mockPool.getConnection();
System.out.println("拿到连接:" + connection.getConnectionName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("归还连接:" + connection.getConnectionName());
mockPool.free(connection);
}).start();
}
}
}
final class MockPool {
private final MockConnection[] connections;
private final AtomicIntegerArray state;
private static final Integer DEFAULT_POOL_SIZE = 5;
private final Semaphore semaphore;
public MockPool() {
connections = new MockConnection[DEFAULT_POOL_SIZE];
state = new AtomicIntegerArray(DEFAULT_POOL_SIZE);
semaphore = new Semaphore(DEFAULT_POOL_SIZE);
initPool();
}
public MockPool(Integer poolSize) {
connections = new MockConnection[poolSize];
state = new AtomicIntegerArray(poolSize);
semaphore = new Semaphore(poolSize);
initPool();
}
private void initPool() {
for (int i = 0; i < connections.length; i++) {
connections[i] = new MockConnection("连接-" + (i + 1));
}
}
@SneakyThrows
public MockConnection getConnection() {
semaphore.acquire();
for (int i = 0; i < connections.length; i++) {
if (state.get(i) == 0) {
if (state.compareAndSet(i, 0, 1)) {
return connections[i];
}
}
}
//一定不会执行到这里
return null;
}
public void free(MockConnection connection) {
for (int i = 0; i < connections.length; i++) {
if (connections[i] == connection) {
state.set(i, 0);
semaphore.release();
return;
}
}
System.out.println("要释放的连接不存在");
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class MockConnection {
private String connectionName;
}
4.3、原理
4.3.1、加锁解锁流程
Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后 停车场显示空余车位减一 ,刚开始,**permits(state)**为 3,这时 5 个线程来获取资源
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);
}
}
//实际上把 permits 存入了 state中
Sync(int permits) {
setState(permits);
}
- 假设 t1 t2 t4 竞争锁成功,t3 t5 竞争失败,进入 AQS 阻塞队列 park:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//只要获取锁成功,都是正数,第四次就会判断成功,进入下面方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
//前三次没有问题,第四次来了后,减为了-1
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
//直接就返回了-1
return remaining;
}
}
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;
}
}
//设置为 -1
if (shouldParkAfterFailedAcquire(p, node) &&
//park住
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 这时 t4 释放 Semaphore
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
//0 + 1 = 1
int next = current + releases;
if (next < current) // 溢出
throw 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))
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;
}
}
- 现在唤醒头节点的下一个节点 t3,重新进入 for 循环,获取锁,然后执行 setHeadAndPropagate 方法,唤醒后面的所有共享节点,但是没有用,因为是 t3 自己先获取了再唤醒的,此时还是为 0,所以 t5 再次 park
5、CountDownLatch
用来进行线程同步协作,等待所有线程完成倒计时。 其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
log.info("begin...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
}).start();
new Thread(() -> {
log.info("begin...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
}).start();
new Thread(() -> {
log.info("begin...");
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
}).start();
log.info("waiting...");
latch.await();
log.info("wait end...");
其实它应该算是 JUC 中最简单的一个工具了,源码如下,有了前面各种工具的底层原理分析,它很容易就读明白的
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
就目前实现的 Demo 我们发现,其效果和 join 的效果一样,那计数器存在的意义在哪,其实,真实场景下,我们的线程都是从线程池中获取的,从而达到线程重用,某些固定大小的线程池,其中的线程还会一直的运行,并不会轻易结束,所以就不能使用 join 这种底层的 API 去实现,下面实现一个使用线程池来倒计时的简单应用
CountDownLatch latch = new CountDownLatch(3);
ExecutorService service = Executors.newFixedThreadPool(4);
service.submit(() -> {
log.info("begin...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
});
service.submit(() -> {
log.info("begin...");
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
});
service.submit(() -> {
log.info("begin...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
});
service.submit(() -> {
try {
log.info("waiting...");
latch.await();
log.info("wait end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
6、CyclicBarrier
6.1、问题提出
- 在使用 CountDownLatch 的时候,我们在执行过程中,CountDownLatch 创建了一次后,主线程必须等到其减为 0 才能继续运行,现在有一个新的需求,这一次执行我需要重复执行三遍,主线程也必须重复等待三次,通常的做法就是使用 for 循环解决:
ExecutorService service = Executors.newFixedThreadPool(4);
for (int i = 0; i < 3; i++) {
CountDownLatch latch = new CountDownLatch(3);
service.submit(() -> {
log.info("begin...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
});
service.submit(() -> {
log.info("begin...");
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
});
service.submit(() -> {
log.info("begin...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
log.info("end...{}", latch.getCount());
});
try {
latch.await();
log.info("finish,{}", latch.getCount());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
但是我们发现,每次循环都创建了新的计数器,而计数器本身并没有方法可以直接更改其中的计数,那有没有更好的方式解决这个问题呢
6.2、基本使用
CyclicBarrier:循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行
所以上面的问题,我们就可以使用这个工具来解决
// 个数为2时才会继续执行
CyclicBarrier cb = new CyclicBarrier(2, () -> {
System.out.println("task1 task2 end");
});
for (int i = 0; i < 3; i++) {
int j = i;
new Thread(() -> {
System.out.println("线程" + j + "-1开始.." + new Date());
try {
// 当个数不足时,等待
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程" + j + "-1继续向下运行..." + new Date());
}).start();
new Thread(() -> {
System.out.println("线程" + j + "-2开始.." + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// 2 秒后,线程个数够2,继续运行
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程" + j + "-2继续向下运行..." + new Date());
}).start();