本文主要是在记录AQS
相关知识点的时候,想起来一直消化的都是别人的文章,但是从来没有系统的看过源码分析,也没有自己去看到相关源码,于是就简单去看了下加锁和释放锁的核心实现,这里仅供记录,毕竟只看了半天,大佬轻喷。
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
}
ReentrantLock
实现Lock
接口去实现加锁和释放锁的具体实现
核心功能通过内部类Sync对象
实现,而Sync
这个类继承AbstractQueuedSynchronizer
,这个即使大名鼎鼎的AQS
,
ReentrantLock
的公平锁和非公平锁通过两个静态内部类FairSync
和NonfairSync
再去继承Sync
重写获取锁的方法,
这是由于AQS
对尝试获取锁和释放锁的逻辑是预留给子类去重写的,父类中的原方法会直接抛出异常
// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread
}
}
AbstractQueuedSynchronizer
继承java.util.concurrent.locks.AbstractOwnableSynchronizer
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
}
上述类的关键点
AbstractQueuedSynchronizer
使用volatile
维护获取对象锁的等待队列,通过链表实现维护头节点head
和尾节点tail
,每个要获取对象锁的线程都会被包装成java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
对象AbstractQueuedSynchronizer
使用volatile
修饰int变量state
, 用来记录同步锁的状态, 0代表未被线程持有,大于0代表对象锁已被线程持有,因为支持重入锁这个int的值即为被同一个线程持有锁的次数,同一个线程多次获取对象锁递增1,释放锁递减1,直到值为0则锁完成释放AbstractQueuedSynchronizer
继承AbstractOwnableSynchronizer
使用Thread类型的变量exclusiveOwnerThread
记录获取到对象锁的线程,这个变量没有使用volatile
修饰,是因为这个变量赋值的前提是必须先获取到对象锁,而获取到对象锁已经决定了这个操作不会存在并发问题了java.util.concurrent.locks.AbstractQueuedSynchronizer.Node#waitStatus
节点的状态来决定线程在获取不到同步锁时是否需要调用LockSupport的park沉睡以及当调用释放锁的方法释放成功后调用LockSupport的unpark方法来唤醒线程,让沉睡的线程重新参与竞争我们主要先以非公平锁为切入点,ReentrantLock
默认就是非公平锁,可以通过构造函数指定为公平锁。
看下Lock
接口的一个实现类ReentrantLock
,
我们来看一下加锁的方法,非公平锁通过ReentrantLock
的内部类NonfairSync
实现,如果获取锁成功,记录获取锁的线程
// java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// 通过cas修改state的值来尝试加锁
if (compareAndSetState(0, 1))
// 获取到锁之后记录当前加锁线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取不到锁的逻辑
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
看下关键的代码compareAndSetState
调用的还是AQS
的代码
// java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetState
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
通过CAS
修改state
的值如果为0则代表没有线程持有锁,然后修改为1,代表当前当前对象锁已经被持有,其它线程再竞争都要进入等待队列中。注意这个state
的值不一定只有0和1。如果加锁成功,则将当前线程赋值给AbstractOwnableSynchronizer
的exclusiveOwnerThread
属性,用来记录当前持有对象锁的线程。
获取锁的方法非常简单,复杂的在如果没有获取到的处理。我们再看下上面的NonfairSync
的lock
方法,如果没有加锁成功,则会调用acquire
方法
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
父类的tryAcquire
方法会抛出异常,所以是留给子类实现的。还是来看非公平锁的实现,默认是ReentrantLock
的内部类Sync
,这里会再次尝试去获取一次锁
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果锁没有被任何线程持有
if (c == 0) {
// 通过CAS去加锁
if (compareAndSetState(0, acquires)) {
// 获取成功记录加锁线程
setExclusiveOwnerThread(current);
return true;
}
}
/**
* 如果锁已经被线程占有,但是持有锁的线程是当前线程(这里就可以看出来ReentrantLock支持可重入锁),则
* 获取锁成功,且对锁的状态字段进行累加,相对的释放的时候也是递减释放的
*/
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;
}
先获取到当前要获取锁的线程,然后再次判断对象锁是否被线程持有,如果没有持有,则直接加锁成功。
加锁成功记录加锁的线程。
如果还是加锁失败,则判断当前线程是否是正在持有对象锁的线程,如果是的话,则state
持有次数累加1(1是传过来的值),然后将state
再次刷回主存(volatile
修饰),然后还是加锁成功。从这里就可以看出来ReentrantLock
支持可重入锁。相对的当释放锁的时候,如果多个同步方法所需要的同步锁对象是同一个,加锁的时候每进入一次同步方法,则state
累加1,而当每个同步方法执行结束的时候则依次递减1,而不是直接将锁完全释放。
tryAcquire如果还是没有获取到锁,则将当前线程包装成java.util.concurrent.locks.AbstractQueuedSynchronizer.Node对象加入到等待队列,等待队列是一个链表,维护了头部和尾部,采用了尾插法.代码在AbstractQueuedSynchronizer
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
// 包装Node节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
/**
* 判断当前的等待队列尾部是否为空,如果不为空(说明之前已经完成过链表的初始化工作),则快速将之前的队
* 列尾部的next指向当前线程,当前线程再指向尾部节点,然后返回,即完成等待队列的插入
*/
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 初始化链表,详解在下面
enq(node);
return node;
}
/**
* java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
* 下面这段代码当时看了很多遍,忽略了循环,导致很多地方对不起来非常重要,要仔细看
* 第一次调用上面的addWaiter方法的时候,tail和head都为空,所以一定会进入到enq方法
* 所以当程序开始执行的时候,一定是进入t==null这个判断的,然后就是初始化一个空的Node,new Node(),
* 然后tail=head=new Node()。这里看的时候联系到后面好多地方取node的prev发现根本和程序对不上,但却忽
* 略了当前方法是个循环,这一步执行结束并没有终结循环,所以程序还会再次进入的。
* 第二次进入之后,t的值就是第一次循环赋值的空node,但不在是null了,然后进入下面的else分支, 于是变成
* 当前Node的prev(其实就是head)是第一次循环的空node,然后tail被重新赋值为当前node,head的next指向
* 当前节点,head保持不变, 关系如下
* head ===> new Node()
* head.next = 当前线程node = tail
* 当前线程node.prev = head
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
// 上面的addWaiter是把当前线程维护到等待队列的尾部,返回node,然后调用当前方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前节点的prev节点,如果为空,则抛出空指针异常
// java.util.concurrent.locks.AbstractQueuedSynchronizer#enq会保证不为空
final Node p = node.predecessor();
/**
* 如果当前节点的prev == head, 则说明当前节点是链表的第二个节点
* 1. java.util.concurrent.locks.AbstractQueuedSynchronizer#enq这个方法决定了
* 第一个没有获取到锁的线程节点head此时是一个空节点,然后实际上存储第一个等待的线程就是
* head.next,然后等锁释放后,正好第一个线程的prev=head,也就是要唤醒的线程,唤醒后
* 去获取锁,如果成功将当前节点指向head,作为目前唯一一个有效的等待线程节点获取到锁之
* 后,其实整个等待队列就没有意义了,因此将head=当前线程节点(获取到锁的),
* p.next = null,其实就是head.next为空了,也就是等待队列空了
* 2. 当前node是addWaiter()方法执行完成后将Node传递进来的,只有第一个等待线程节点的.prev
* 是head,所以这个判断只有创建好第一个等待线程节点的时候才会执行
*/
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/**
* 有一个字段waitStatus,描述了如果当前线程到这里还没获取到对象锁,就要将对象设置为SIGNAL
* 即等待状态,然后等待持有对象锁的线程对锁对象执行unpark释放锁,否则这里的线程就要沉睡,
* 沉睡需要等待释放锁之后的唤醒,由于这里是个死循环,唤醒之后继续去竞争所
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
看下线程获取不到锁对waitStatus
值的处理
// java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 如果线程需要执行唤醒操作,则调用LockSupport.park方法先将当前线程沉睡
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
附录waitStatus
的值含义java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
释放锁直接调用的就是java.util.concurrent.locks.ReentrantLock#unlock
方法内部调用的是java.util.concurrent.locks.AbstractQueuedSynchronizer#release
tryRelease
父类默认没有实现抛出的异常,所以我们还是来看非公平锁的实现java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
// `java.util.concurrent.locks.ReentrantLock#unlock`
public void unlock() {
// 每次只将记录同步锁的state的值递减1
sync.release(1);
}
// `java.util.concurrent.locks.AbstractQueuedSynchronizer#release`
public final boolean release(int arg) {
// 释放锁
if (tryRelease(arg)) {
Node h = head;
// 如果头节点不为空则说明当前等待队列有线程等待,需要执行唤醒操作
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// `java.util.concurrent.locks.ReentrantLock.Sync#tryRelease`
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
的值递减1,这是由于ReentrantLock
对重入锁的支持,同一个线程执行多个需要同一个对象锁的代码时,多次获取锁的时候会将同步状态state
累加,所以释放的时候可以理解为其中某一个同步代码块/方法执行完毕,然后释放本代码块/方法的锁,所以要依次递减释放,而不能一下子全部释放掉。
然后判断当前线程是否是持有对象锁的线程,如果不是,则不允许执行释放操作。
而当同步状态最后一次释放到为0的时候,则说明此时对象锁不再被任何线程持有,需要清空保存持有对象锁线程的变量exclusiveOwnerThread
,最后将state
的最新值写回内存。
tryRelease
方法执行完成之后,则释放了对象锁。之前我们看加锁过程中的时候是有一批获取不到对象锁的线程的waitStatus
被标记为了SINAL
状态的,即线程执行了park
即等待操作,现在对象锁已经释放,需要对之前要获取对象锁的线程执行唤醒操作。判断当前等待队列的head
是否为空以及是否需要执行唤醒操作,如果满足条件的话执行唤醒操作。
看下上面tryRelease
方法执行成功后的unparkSuccessor
方法
// java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
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);
}
再贴一遍waitStatus
值的含义,我们目前看的加锁释放锁只牵扯到了SIGNAL
,前面加锁的时候已经解释过。
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
unparkSuccessor
方法传入的Node
是头节点, 判断要唤醒的线程的waitStatus
如果小于0,则需要重置修改为0。
s == null
条件成立的话,为什么还要倒序遍历呢?有点没看懂,不是双向链表吗?
判断头节点是否存在next
节点,以及如果存在next
节点状态是否是CANCELLED
(从上面状态含义看大于0的只有这一个),如果满足条件,则从尾部开始向前遍历,找到最后一个waitStatus<=0
的节点,然后唤醒这个线程。
这它喵的是个啥意思??就唤醒一个最初插入的节点,咋这么像公平锁呢??
这里理解一下,感觉公平不公平还得看同行衬托
非公平锁的lock
方法
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
公平锁的lock
方法
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
}
这就看出来区别了,非公平锁在加锁的时候,会让给当前线程一次机会直接参与对state
的竞争修改,如果能够竞争成功,则不需要进入等待队列
而公平锁则少了非公平锁的第一步,只能老老实实的直接执行acquire
方法
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
之前已经说过了java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
方法是留给父类重写的,现在要看下公平锁的实现了
// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
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;
}
// 对比非公平锁,在c==0获取锁的时候多了一个方法hasQueuedPredecessors()
// java.util.concurrent.locks.AbstractQueuedSynchronizer#hasQueuedPredecessors
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;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
这里就看到了公平锁与非公平锁在当前锁处于空闲的时候,多执行了一个方法hasQueuedPredecessors
哎,智商捉急,这个方法返回取反,判断在各种反向判断并且或者一大堆的就脑袋瓜子嗡嗡的。
这他么这点代码真是让我绕道沟里去了,比看前面代码还费劲。
这里还是去搜索了一下有没有人有相关的疑惑,搜到了这篇文章
请的外援
hasQueuedPredecessors条件不成立则执行获取锁操作,
h !=t 条件不成立则是h == t,
tail==head条件成立的情况下是等待队列里只有一个线程节点,且这个节点目前获取到了锁,具体看java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
之前分析的代码。
下面的判断就看不懂了,交给上面搜索的文章