Java线程之JUC中的锁
一、AQS
1. 基本概念
- AQS:全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
2. 特点
- 用
state
属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何通过该属性获取锁和释放锁。 - 一般用
getState
来获取state
状态,用setState
来设置state
状态。还可以用compareAndSetState
来使用CAS机制设置state
状态。 - 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源。
- 提供了基于FIFO的等待队列,类似于Monitor的EntryList。
- 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet。
3. 细节描述
3.1 AQS的子类
AQS的子类主要实现了以下的方法:① tryAcquire ② tryRelease ③ tryAcquireShared ④ tryReleaseShared ⑥ isHeldExclusively。如果子类不实现而直接调用该方法,则会抛出异常。
3.2 获取锁和释放锁
AQS中暂停和恢复线程实际上使用的是park
和unpark
机制。
- 获取锁的姿势
// 如果获取锁失败 if (!tryAcquire(arg)) { // 入队, 可以选择阻塞当前线程 }
- 释放锁的姿势
// 如果释放锁成功 if (tryRelease(arg)) { // 让阻塞线程恢复运行 }
4. AQS自定义不可重入锁
class MyLock implements Lock {
//独占锁,同步器类
class MySync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0, 1)){
//如果加锁成功,则设置owner为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);//注意先后顺序,应该把对state的设置放在后面,利用state的volatile的写屏障,保证之前的变量也是对其他线程可见的
return true;//只有加了锁的线程才能解锁,所以操作必定成功
}
@Override
protected boolean isHeldExclusively() {//是否独占锁
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private MySync sync;
@Override
public void lock() {
sync.acquire(1);//acquire调用tryAcquire
}
@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);//release方法除了调用tryRelease方法,还会唤醒阻塞的线程
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
二、ReentrantLock
1. 非公平锁
1.1 加锁成功流程
- 没有竞争时,线程Thread-0到来即可获取该锁,即加锁成功。
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
1.2 加锁失败流程
- 当Thread-0获取该锁之后,Thread-1到来,此时,尝试利用CAS将state由0改为1,结果失败。
- 由此,则进入了acquire逻辑中的tryAcquire逻辑,tryAcquire逻辑中会再次尝试加锁一次,但此时state已经是1,结果仍然失败。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){ selfInterrupt(); } }
- 接下来进入addWaiter逻辑,构造Node队列来存放阻塞的线程。① 图中黄色三角表示该Node的waitStatus状态,其中0为默认正常状态。② Node的创建是懒惰的。③ 其中第一个Node称为Dummy(哑元)或哨兵,用来占位,并不关联线程。
注意上图中head就是Dummy,它也是Node(null),tail就是Node(Thread-1),addWaiter逻辑中完成的逻辑实际上就是将新进入阻塞队列的线程放在tail的位置。即如下的情况
- 随后,当前线程进入acquireQueued逻辑,acquireQueued会在一个死循环中不断尝试获得锁,失败后进入park阻塞。逻辑如下:① 如果自己是紧邻着head(排第二位),那么再次tryAcquire尝试获取锁,当然这时state仍为1,失败。 ② 进入shouldParkAfterFailedAcquire逻辑,将前驱node,即head的waitStatus改为 -1,这次返回false,不会执行parkAndCheckInterrupt逻辑进入阻塞。注意,-1表示由该节点唤醒该节点之后的节点内的线程。③ 由于死循环,再次进入tryAcquire尝试获取锁,当然这时state仍为1,失败。 ④ 当再次进入shouldParkAfterFailedAcquire时,这时因为其前驱node的waitStatus已经是 -1,这次返回true ⑥ 进入parkAndCheckInterrupt逻辑, Thread-1 park(灰色表示)
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); } } }
- 当多个线程竞争失败之后,队列中变成如下的情况
1.3 解锁成功流程
- Thread-0进入release逻辑释放锁,逻辑如下:① 进入tryRelease流程,设置exclusiveOwnerThread为null,同时将state设置为0。②此时head为null且waitStatus为-1,满足条件则进入unparkSuccessor逻辑,将队列中离head最近的下一个未取消节点的线程唤醒。本例中为Thread-1。
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; }
- Thread-1从阻塞(parkAndCheckInterrupt)中出来以后,如果没有其他线程竞争,则Thread-1在for死循环中将exclusiveOwnerThread修改为Thread-1,将state修改为1,加锁成功。同时,还会将Thread-1节点设置为头节点(涵盖了将当前节点内的Thread-1变成null,prev变成null,head变成该node的操作),并断开和原头节点的连接。
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); } } }
private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
1.4 解锁失败流程
- 如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4来了,如果不巧又被 Thread-4占了先Thread-4被设置为 exclusiveOwnerThread,state = 1,则Thread-1再次重复加锁失败的流程,即进入acquireQueued流程,获取锁失败,重新进入park阻塞。
2. 可重入原理
ReentrantLock可重入锁住就是利用state控制锁的重入次数
- 加锁时,每重入一次,则state加1。
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) {// overflow throw new Error("Maximum lock count exceeded"); } setState(nextc); return true; } return false; }
- 解锁时,每解一次state减1,直到state减到0,才真正的解开了锁。
// Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) { // state-- int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()){ throw new IllegalMonitorStateException(); } boolean free = false; // 支持锁重入, 只有 state 减为 0, 才释放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
3. 可打断原理
3.1 不可打断原理
只有成功的获取锁以后,才可以返回打断状态。
private final boolean parkAndCheckInterrupt() {
// 如果打断标记已经是 true, 则 park 会失效
LockSupport.park(this);
// interrupted 会清除打断标记
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;
failed = false;
// 还是需要获得锁后, 才能返回打断状态
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
// 如果是因为 interrupt 被唤醒, 返回打断状态为 true
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 如果打断状态为 true
selfInterrupt();
}
}
static void selfInterrupt() {
// 重新产生一次中断
Thread.currentThread().interrupt();
}
3.2 可打断原理
没有成功的获取锁以后,被打断就会直接抛出异常
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()) {
// 在 park 过程中如果被 interrupt 会进入此
// 这时候抛出异常, 而不会再次进入 for (;;)
throw new InterruptedException();
}
}
} finally {
if (failed){
cancelAcquire(node);
}
}
}
4. 公平锁
公平锁与非公平锁的区别主要在于tryAcquire逻辑,对于非公平锁,新到的线程(非队列中的线程)不会管AQS队列中是否存在先到的线程,直接开抢。但是公平锁则不同,它会先判断队列中是否有先到的线程,如果有,自己不能抢。
// 与非公平锁主要区别在于 tryAcquire 方法的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
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;
}
// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// h != t 时表示队列中有 Node
return h != t && (
// (s = h.next) == null 表示队列中还有没有老二
(s = h.next) == null ||
// 或者队列中老二线程不是此线程
s.thread != Thread.currentThread()
);
}
5. 条件变量
前言
- 每个条件变量其实就对应着一个等待队列,其实现类是ConditionObject。
5.1 await流程
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted()){
interrupted = true;
}
}
if (acquireQueued(node, savedState) || interrupted){
selfInterrupt();
}
}
- 开始Thread-0持有锁。
- 调用await,进入ConditionObject的addConditionWaiter流程,此时会创建新的Node,其状态设为-2(Node.CONDITION),该Node关联Thread-0,加入等待队列尾部。注意,这个Node(Thread-0)即是firstWaiter也是lastWaiter。
private Node addConditionWaiter() { Node t = lastWaiter; // If lastWaiter is cancelled, clean out. if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; }
- 接下来进入AQS的fullyRelease流程,其内部逻辑如下:① 调用了release方法,释放同步器上的所有锁(为了有重入锁的时候,全部释放)。② 在release方法中,唤醒下一个阻塞的节点。
final int fullyRelease(Node node) { boolean failed = true; try { 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; }
- 下一个节点竞争锁,假设没有其他竞争线程,那么Thread-1竞争成功。
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); } } }
- 调用park方法使当前线程(本例中的Thread-0)陷入阻塞。
5.2 signal流程
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
-
假设当前的Thread-1要唤醒Thread-0。
-
进入ConditionObject的第一个Node的doSignal流程。该流程逻辑如下:① 将当前的Node从链表中断开 ② 进入transferForSignal逻辑,将该断开的节点的waitStatus状态设置为0,转入到竞争锁的链表的尾部,并将前一个节点的waitStatus状态设置为-1。
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 (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }