AQS源码
1.三种线程调度方式
-
wait()/notify()/notifyAll()
public static void main(String[] args) { Object obj = new Object(); new Thread(()->{ synchronized (obj){ System.out.println("线程[{}]"+Thread.currentThread().getName()+"获取到锁,并且暂停"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程[{}]"+Thread.currentThread().getName()+"获取到锁,继续执行"); } },"thread-1").start(); new Thread(()->{ try { //暂停1秒,保证线程1能拿到锁 Thread.sleep(2000); synchronized (obj){ System.out.println("线程[{}]"+Thread.currentThread().getName()+"获取到锁,并唤醒等待的线程"); //唤醒等待的线程1 obj.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } },"thread-2").start(); }
-
await()/signal()/signalAll()
public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(()->{ try { lock.lock(); System.out.println("线程[{"+Thread.currentThread().getName()+"}]获取到锁,并执行条件等待"); condition.await(); System.out.println("线程[{"+Thread.currentThread().getName()+"}]取消条件等待,继续执行"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"thread-1").start(); new Thread(()->{ try { Thread.sleep(1000); System.out.println("线程[{"+Thread.currentThread().getName()+"}]暂停1秒"); lock.lock(); condition.signal(); System.out.println("线程[{"+Thread.currentThread().getName()+"}]解除条件等待,唤醒等待线程"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } },"thread-2").start(); }
-
LockSupport.park()/LockSupport.unpark()
public static void main(String[] args) { Object obj = new Object(); Thread t1 = new Thread(()->{ try { Thread.sleep(2000); System.out.println("线程[{"+Thread.currentThread().getName()+"}]进入执行"); LockSupport.park(obj); System.out.println("线程[{"+Thread.currentThread().getName()+"}]继续执行"); } catch (InterruptedException e) { e.printStackTrace(); } },"thread-1"); Thread t2 = new Thread(()->{ try { Thread.sleep(1000); System.out.println("线程[{"+Thread.currentThread().getName()+"}]暂停1s,并执行unpark"); LockSupport.unpark(t1); } catch (InterruptedException e) { e.printStackTrace(); } },"thread-2"); t1.start(); t2.start();
用来创建锁和其他同步类的基本线程阻塞原语。类似于信号量
Semaphore
,但是只有一个许可,且可以先释放再获取许可。
2.CAS
全称Compare And Swap
,比较并交换。
通过sun.misc.unsafe
类实现,主要参数有三个,分别是内存值、期望值以及更新值,只有内存值和期望值一致时才会将内存值修改为更新值。
例如JUC包下的atomicXXX等类均通过CAS的方式实现。
3.volitale
-
Java内存模型(JMM)
graph TD A(线程A)-->B(本地内存A) C(线程B)-->D(本地内存B) B --> |共享变量副本A| F(主内存&&共享变量) D --> |共享变量副本B| F -
volitale
- 共享变量可见性
- 防止指令重排
4.AQS
4.1 简介
AQS全称是AbstractQueuedSynchronizer
,抽象的队列同步器,提供了一个实现阻塞锁(ReetrentLock
)或者相关的同步器(semaphore
、countDownLatch
)的框架,这些锁或者同步器都依赖于一个FIFO的等待队列,并且可以通过一个int
类型的变量来表示锁或者同步器的状态。
AQS定义了2种资源共享模式:
- 独占模式(Exclusive):独占,只有一个线程能执行,如
ReentrantLock
- 共享模式(Share):共享,多个线程可同时执行,如
ReadWriteLock
、Semaphore
等
4.2 源码分析
4.2.1 state
/**
* 同步状态
*/
private volatile int state;
表示锁或者同步器的状态,不同的锁或者同步器对于state的值可能都不同,且各个值表示的含义也不同。
state
使用volatile
关键字修饰,能够保证state的可见性,state
的访问方式有三种:
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
state不同的值表示不同的锁状态,通常使用cas的方式设置state
的值,
比如ReetrantLock
中state>0
表示锁已经被抢占,其他线程只能进入队列等待唤醒,state=0
表示占用锁的线程已经释放锁了,其他线程可以进行抢占。
再比如Semephore
中state
的初始值就是设置的许可数量,每使用一个许可就将state
值减1,当state=0
时表示许可用完,后续线程就需要排队。
4.2.2 Node
竞争资源失败的线程需要进入等待队列(CLH队列),在进入等待队列之前被封装成一个个的Node
,Node
主要包含以下几个属性:
/** 表示节点处于共享模式 */
static final Node SHARED = new Node();
/** 表示节点处于独占模式 */
static final Node EXCLUSIVE = null;
/**节点状态*/
volatile int waitStatus;
/**前置节点*/
volatile Node prev;
/**后置节点*/
volatile Node next;
/**当前线程*/
volatile Thread thread;
/**条件队列中的下一个节点*/
Node nextWaiter;
其中表示节点状态的waitStatus
属性的值为以下几种,新创建的节点waitstatus
的值是0.
/** 表示线程已经被取消了(timeout或者被中断) */
static final int CANCELLED = 1;
/** 表示后置节点在等待前置节点唤醒,后继节点入队时,会将前置节点的状态修改为 SIGNAL*/
static final int SIGNAL = -1;
/** 表示线程正在等待condition上,当其他线程调用了Condition.signal()方法时,线程会从条件队列转移到等待队列,等待获取同步锁 */
static final int CONDITION = -2;
/**
* 共享模式下,前置节点不仅要唤醒后继节点,还要唤醒后继节点的后继节点
*/
static final int PROPAGATE = -3;
waitStatus
<0时表示线程还在有效等待,但是waitStatus
>0时表示timeout或者线程中断,因此源码中很多地方使用>0或者<0的方式进行判断,并没有详细到每一个值的判断。
4.2.3 CLH队列
4.2.3.1 原始CLH队列
CLH队列全称是Craig, Landin, Hagersten lock queue
,是一个基于(隐式)单向链表的可扩展、高性能、公平的自旋锁,能够保证无饥饿,并且先到先得的公平性,通过在后继节点中自旋前继节点中的属性值来实现。
代码示例:
/**
* 定义Node
*/
public class QNode {
volatile boolean locked;
}
/**
* 定义Lock接口
*/
public interface Lock {
void lock();
void unlock();
}
/**
* 定义CLH锁
*/
public class CLHLock implements Lock {
// 尾节点,是所有线程共有的一个。所有线程进来后,把自己设置为tail
private final AtomicReference<QNode> tail;
// 前驱节点,每个线程独有一个。
private final ThreadLocal<QNode> myPred;
// 当前节点,表示自己,每个线程独有一个。
private final ThreadLocal<QNode> myNode;
public CLHLock() {
this.tail = new AtomicReference<>(new QNode());
this.myNode = ThreadLocal.withInitial(QNode::new);
this.myPred = new ThreadLocal<>();
}
@Override
public void lock() {
// 获取当前线程的代表节点
QNode node = myNode.get();
// 将自己的状态设置为true表示获取锁。
node.locked = true;
// 将自己放在队列的尾巴,并且返回以前的值。第一次进将获取构造函数中的那个new QNode
QNode pred = tail.getAndSet(node);
// 把旧的节点放入前驱节点。
myPred.set(pred);
// 在等待前驱节点的locked域变为false,这是一个自旋等待的过程
while (pred.locked) {
}
// 打印myNode、myPred的信息
peekNodeInfo();
}
@Override
public void unlock() {
// unlock. 获取自己的node。把自己的locked设置为false。
QNode node = myNode.get();
node.locked = false;
myNode.set(myPred.get());
}
}
/**
* 使用场景
*/
public class KFC {
private final Lock lock = new CLHLock();
private int i = 0;
public void takeout() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ": 拿了第" + ++i + "份外卖");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final KFC kfc = new KFC();
Executor executor = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 35; i++) {
executor.execute(kfc::takeout);
}
}
4.2.3.2 变体CLH队列
AQS中使用的CLH队列是原始CLH队列的变体,变体的CLH队列借鉴了CLH队列的思想,即在前置节点中保存一些线程的信息,使用Node
节点中的status
来追踪线程是否应该被阻塞,但是status
的值不能用来控制线程是否应该被锁定。
对于非公平锁,一个线程处于队列的头结点处,但是并不一定能获取锁成功,处于头节点处只是给了一个竞争的权利,如果竞争失败,还是要继续等待。
4.2.4 流程图
4.2.5 源码分析
以ReentrantLock
为例,ReentrantLock
是独占锁,且分为公平锁和非公平锁,本次分析以非公平锁为例。
4.2.5.0 加锁过程分析
lock()
过程
public class ReentrantLock implements Lock, java.io.Serializable {
//构造方法,默认非公平
public ReentrantLock() {sync = new NonfairSync();}
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
//继承AQS
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock(); // <2>
//尝试获取锁
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()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
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;
}
//通过state判断状态
final boolean isLocked() {return getState() != 0;}
}
//非公平锁
static final class NonfairSync extends Sync {
final void lock() { // <3>
//cas的方式修改state的值
if (compareAndSetState(0, 1))
//cas修改成功,设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
else
//cas修改失败
acquire(1); //<4>
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//公平锁
static final class FairSync extends Sync {
final void lock() {acquire(1);}
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 void lock() {sync.lock();} // <1>
public boolean tryLock() {return sync.nonfairTryAcquire(1);}
public void unlock() {sync.release(1);}
}
当出现竞争时,调用AQS.acquire(1)
方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && //尝试竞争锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //加入队列
selfInterrupt(); //线程中断
}
先看AQS中的tryAcquire
方法
// AbstractQueuedSynchronizer
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
是一个由protected
修饰的方法,并且直接抛出异常,这样的方法可以由子类重载,实现具体的逻辑,ReentrantLock.NonfairSync
中的重载方法如下
//NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//Sync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//锁状态
int c = getState();
if (c == 0) { //state值为0,表示锁已经被释放,当前线程可以竞争锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //可重入锁判断,判断当前线程是否是已经设置的独占线程
//可重入锁,state值加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//锁没有被释放,也不是可冲入锁,当前线程获取锁失败,返回false
return false;
}
获取锁失败,!tryAcquire(arg)=true
,执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
方法将当前线程加入到等待队列中。
先看addWaiter(Node.EXCLUSIVE)
方法,设置Node
节点的类型是独占型(Node.EXCLUSIVE
)
//AbstractQueuedSynchronizer.java
private Node addWaiter(Node mode) {
//将当前线程封装成一个mode(独占)类型的节点
Node node = new Node(Thread.currentThread(), mode);
//tail是CLH队列的尾部指针,初始为null,只有在有节点的情况下才不为null(懒加载思想)
//以最快的方式直接将当前节点放置到队尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//直接将节点放置到队尾失败,执行入队操作
enq(node);
//返回入队后的节点
return node;
}
继续查看enq(node)
方法
//AbstractQueuedSynchronizer.java
private Node enq(final Node node) {
//死循环(自旋),直到将node入队才会结束,否则一直循环
for (;;) {
Node t = tail;
if (t == null) { //tail节点为空,表示CLH队列还没有初始化,必须先初始化
if (compareAndSetHead(new Node())) //cas方式创建一个空的Node节点(傀儡节点),并将head指向新创建的空节点
tail = head; //tail也指向新创建的节点
} else {
//CLH队列初始化完成
node.prev = t;
//cas方式将node节点添加到队尾,入队完成
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
Node
节点入队完成后,执行acquireQueued()
方法,具体源码如下
//AbstractQueuedSynchronizer.java
final boolean acquireQueued(final Node node, int arg) {
//标记线程是否获取到锁
boolean failed = true;
try {
//标记等待过程中线程是否被中断
boolean interrupted = false;
//死循环,自旋
for (;;) {
//获取当前节点的前置节点
final Node p = node.predecessor();
//如果前置节点是head,表示当前线程所在节点位于队列中第二个节点(第一个是傀儡节点)
//此时当前线程重新尝试获取锁
if (p == head && tryAcquire(arg)) {
//获取锁成功,表示之前占用锁的线程要么释放锁了,要不就是中断了
//将当前节点设置为头节点,node中的prve和next都设置为null,tread也设置为null,当前节点成为傀儡节点
setHead(node);
p.next = null; // help GC
failed = false; //获取到锁
return interrupted; //未被中断
}
//非首节点
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//在线程等待过程中,如果线程被中断了,interrupted就会被标记为true,但是aqs不处理线程的中断,由线程本身去处理中断状态
//如果线程未被中断,正常情况下会一直等待到被唤醒,重新抢占锁
interrupted = true;
}
} finally {
if (failed)
//取消等待,出现异常时才进入,tryAcquire由子类实现,该方法抛出异常会导致取消节点等待
cancelAcquire(node);
}
}
先看下shouldParkAfterFailedAcquire
方法
//AbstractQueuedSynchronizer.java
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前置节点状态,有5种值:1,-1,-2,-3,0
int ws = pred.waitStatus;
//前置节点状态等于-1,表示当前节点可以被前置节点唤醒,因此当前节点可以正常park()
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {//前置节点状态等于1,表示前置节点已经被中断,需要从当前节点开始向前查找,直到找到未被中断的节点,然后设置为当前节点的前置节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;//找到前置节点中未中断的节点,之前的节点会从队列中出队,被gc掉
} else {
//前置节点状态是0或者-2,-3,此时不能判断当前节点是否可以park,cas的方式将前置节点状态设置为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
一个新的节点,至少要经历两次自旋,shouldParkAfterFailedAcquire
方法才会返回true
,此时就会进入parkAndCheckInterrupt
方法
//AbstractQueuedSynchronizer.java
private final boolean parkAndCheckInterrupt() {
//调用park使线程进入waiting状态(响应中断)
LockSupport.park(this);
//线程被唤醒或者中断后,返回中断标识位并清除,并且park是不响应中断异常的,外部中断只会唤醒等待的线程
return Thread.interrupted();
}
由于LockSupport.park(obj)
方法不响应中断异常,因此acquireQueued()
方法中只有在由子类实现的tryAcquire
方法抛出异常时才会执行取消节点等待的cancelAcquire
方法
//AbstractQueuedSynchronizer.java
private void cancelAcquire(Node node) {
//节点不存在,忽略
if (node == null)
return;
node.thread = null;
// 跳过取消的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//未取消的前置节点的下一个节点
Node predNext = pred.next;
//当前节点的状态值改为取消(1)
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,cas直接删除
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
//如果当前节点的后继节点需要唤醒,那么就cas的方式设置前置节点的后继节点,否则直接唤醒后继节点
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
最后再回到acquire()
方法
//AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
表示尝试去获取锁,acquireQueued
表示在获取锁失败后入队等待,如果当前线程获取锁失败,并且在入队等待过程中被中断,那么就会执行selfInterrupt
进行自我中断
//AbstractQueuedSynchronizer.java
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
此处只是设置了线程的中断状态,中断之后需要如何操作需要在线程中对应编码
4.2.5.1 加锁过程总结
加锁过程中最重要的一个方法就是 tryAcquire
,需要子类自行实现,并且要设置好state的值对应的状态,其余的入队唤醒等操作在aqs中都已经进行了实现,子类不需要再关心。
4.2.5.2 释放过程分析
释放锁的入口是unlock
// ReentrantLock.java
public void unlock() {
sync.release(1);
}
sync
是ReentrantLock
的内部类,继承了AQS类,release
是定义在AQS中的方法
//AbstractQueuedSynchronizer.java
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
Node h = head;
//头节点不为空且状态不等于0(有后继节点入队且需要唤醒时,设置当前前置节点状态为-1)
if (h != null && h.waitStatus != 0)
//唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
根据tryRelease
的返回值来确定释放锁是否成功,tryRelease
也是一个需要子类重载的方法,AQS中的原始方法如下
//AbstractQueuedSynchronizer.java
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
ReentrantLock
中重载的tryRelease
方法
//ReentrantLock.Sync.java
protected final boolean tryRelease(int releases) {
//state值减1
int c = getState() - releases;
//判断当前线程是否是独占线程,不是抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//c==0表示当前线程可以释放锁了
free = true;
//将独占线程设置为null
setExclusiveOwnerThread(null);
}
//设置state的值
setState(c);
return free;
}
//state值使用volatile修饰
protected final void setState(int newState) {
state = newState;
}
释放锁之后,需要判断队列中是否有线程在等待唤醒,如果有的话,需要执行unparkSuccessor
方法唤醒后继节点
//AbstractQueuedSynchronizer.java
private void unparkSuccessor(Node node) {
//node节点为傀儡节点(待释放锁的线程节点,出队后变成傀儡节点)
int ws = node.waitStatus;
//傀儡节点状态小于0时,cas方式修改为0
//失败了也无所谓,新的节点入队时会进行修正
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//傀儡节点的下一个节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {//后继节点为空或者中断了
s = null;
//从tail开始向前遍历,直到找到节点状态小于0的有效节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//有效的后继节点非空,唤醒
LockSupport.unpark(s.thread);
}
4.2.5.3 释放过程总结
release
是独占模式下释放锁的顶层入口,能够释放指定数量的资源(state每次减少的值),在资源完全释放(state=0)时表示当前线程已经完全释放了锁,如果有后续线程需要唤醒就会去唤醒对应的线程。
4.3 应用
工具类 | 锁类型 | state作用 |
---|---|---|
Semephore | 共享锁 | 初始化许可数量 |
CountDownLatch | 共享锁 | 维护计数器 |
ReentrantLock | 独占锁 | 锁重入次数 |
ReentrantReadWriteLock | 读锁:共享,写锁:独占 | 高16位表示共享锁数量,低16位表示独占锁重入次数 |
CyclicBarrier | 共享锁 | 维护计数器 |
4.4 总结
不同的自定义同步器争用共享资源的方式不同,自定义同步器在实现时只需要定义好state
的获取和释放方式即可,具体的线程等待队列的维护AQS已经在顶层实现好了,自定义同步器在定义时其实只需要根据需要实现以下几个方法即可:
tryAcquire(int)
: 独占模式获取资源,成功返回true
,失败返回false
;tryRelease(int)
: 独占模式释放资源,成功返回true
,失败返回false
;tryAcquireShared(int)
: 共享模式获取资源;tryReleaseShared(int)
: 共享模式释放资源;
参考文档: