[Java] 简单分析Lock锁lock方法以了解AQS
参考
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
https://blog.csdn.net/qq_29373285/article/details/85164190
java提供了两种方式来加锁,一种是关键字:synchronized,一种是concurrent包下的lock锁。
synchronized是java底层支持的,而concurrent包则是jdk实现。
具体流程如下:
什么是AQS
Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
看一下从Lock类到AQS以及上层的AbstractOwnableSynchronizer继承关系
说明:
- ReentrantLock实现了Lock接口,Sync、NonfairSync以及FairSync都是其内部类
- NonfairSync和FairSync是公平锁和非公平锁
- Sync继承自AQS,这是AQS的核心类
- 顶层抽象类AbstractOwnableSynchronizer用来保存线程
new ReentrantLock()发生了什么
我们简单的使用lock锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
//TODO: 同步代码块
lock.unlock();
}
}
看一眼ReentranLock()的构造方法
public ReentrantLock() {
sync = new NonfairSync();
}
可见,来进行加锁操作的实际上是ReentrantLock中的内部类,且默认是非公平锁
当使用lock.lock()发生了什么
public void lock() {
sync.lock();
}
可见,也是用sync属性来进行加锁,sync是ReentrantLock中的final属性,那sync是如何加锁的?
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
@ReservedStackAccess
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
简单说明:
- compareAndSetState(0,1)这是继承自AQS类的方法,即使用CAS原理修改AQS中的state属性
- setExclusiveOwnerThread方法指修改成功,即把当前线程用此类存储
- 如果修改不成功则执行acquire方法
说明:通过修改了AQS的属性state来进行加锁
我们再看看acqure方法
@ReservedStackAccess
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
-
tryAcquire:会尝试再次通过CAS获取一次锁。
-
addWaiter:将当前线程加入上面锁的双向链表(等待队列)中
-
acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
具体的addWaiter和acquireQueued方法贴在下面,可以看到CAS原理的确是Java并发的基石
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);
return node;
}
@ReservedStackAccess
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);
}
}
AQS类中有什么?
通过上述分析,可以看到加锁不成功时,或将当前线程放入AQS的等待队列中,那么这个等待队列是如何定义的呢?
以下为省略的AQS类代码
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
protected AbstractQueuedSynchronizer() { }
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
//省略代码无数
}
说明:
- 可以看到AQS其实是一个类似于双向链表的类
- 其中存储了双向链表的头结点与尾结点
- 链表的节点是AQS的内部类Node,其thread属性用来保存线程信息
这样的话,可以与之前的addWaiter方法或acquireQueued方法对应,其实就是把为成功加锁的线程加入到双向链表的尾部,以及链表头部的节点自旋加锁的过程。