我们使用ReentrantLock的时候一般是如下用法
public class MainActivity {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
//逻辑代码
}finally {
lock.unlock();
}
}
}
今天来看看其实现,首先从构造函数看起,主要有两个构造函数
public ReentrantLock() { //默认构造函数,创建的是非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {//带布尔值的构造函数,可以选择创建公平锁还是非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
正常来说我们会先调用ReentrantLock.lock();接下来看其代码实现
public void lock() {
sync.lock();
}
可以看到上述代码调用了sync的lock方法,那么这个sync是什么呢?代码如下
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
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;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
从上述代码中可以得知,sync是一个抽象内部类,至于这个类的逻辑是什么,这里先跳过。我们先找这个抽象内部类的实现类。 这个抽象类的实现类有两种,一种是非公平锁,一种是公平锁,在这里我们只关注非公平锁的实现,代码如下
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);
}
}
代码追到这里,我们也就发现了调用ReentrantLock.lock();其实到最后调用的是NonfairSync(非公平锁)的lock方法。接下来看看非公平锁这个lock方法的一些细节。代码如下:
final void lock() {//这里的if 体现出了非公平锁
if (compareAndSetState(0, 1))//使用CAS来设置State这个属性的值,从0设置为1,设置成功表示当前线程拥有这个锁
setExclusiveOwnerThread(Thread.currentThread());//将持有锁的持有者设置为当前线程
else
acquire(1);//如果上述CAS设置失败则跑这里
}
看完上述的代码,大家可能会有的疑问,1、compareAndSetState(0, 1)这个函数的作用是什么?2、state是什么?3、acquire这个函数的作用是什么?接下来按顺序解答大家的疑问。首先看compareAndSetState(0, 1)
protected final boolean compareAndSetState(int expect, int update) {
return U.compareAndSwapInt(this, STATE, expect, update);//使用原子操作来更新state的值
}
//可能大家有疑惑,这里的U是什么,它的声明如下
//private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
//是作用就是:JDK 的 rt.jar 包中的 sum.msic.Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是native 方法,它们使用 JNI 的方式访问本地C++ 实现库
看完了第一个疑问,接下来来看第二个问题:state是什么?
private static final long STATE;//用来表示锁状态的,0表示未被加锁,大于0表示已经加锁,已被占用
static {
try {
STATE = U.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
HEAD = U.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
TAIL = U.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
Class<?> ensureLoaded = LockSupport.class;
}
接下来看第三个问题:acquire函数的作用是什么?代码如下
//第一步
public final void acquire(int arg) {
if (!tryAcquire(arg) &&// 这里可以看到首先调用了tryAcquire,代码贴在下面(看第二步),tryAcquire如果没获得锁,则return false,取反则为true
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//如果在执行acquireQueued有人中断过该线程,则会调用selfInterrupt来中断线程
}
//第二步 这个函数主要封装了一些加锁的逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//获取state的值
if (c == 0) {//再次比较是否为0
if (!hasQueuedPredecessors() && //这个方法相对复杂,下面单独贴出来分析(第三步)
compareAndSetState(0, acquires)) {//再次使用CAS尝试加锁,试图将state设置为1
setExclusiveOwnerThread(current);//设置当前线程为获取锁的线程,这里是为了实现可重入
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//当state不等于0的时候,也可能是当前线程设置过这个state的状态,所以会加这个判断
int nextc = c + acquires;//当前线程每重入一次,就会对state的值加1
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);//将更新后的state设置回去
return true;
}
return false;
}
//第三步
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾部节点
Node h = head;//头部节点
Node s;
return h != t &&
((s = h.next) == null ||
s.thread != Thread.currentThread());
}
//首先分析hasQueuedPredecessors返回false的情况
//1、h != t 表示队列存在两个或者以上的元素,并且前两个不等 为true
//2、(s = h.next) == null 表示头节点的第二个元素不为空,并赋值给节点s 所以为false
//s.thread != Thread.currentThread() 表示第二个节点是当前线程了 为false
//hasQueuedPredecessors返回true的情况
//第1.2步和上述一样,最主要还是第3步判断当前线程是不是第二个节点,是的话就可以去获取锁.
//上述第2、第3步都是从第一步的tryAcquire追出来的,看完tryAcquire接下来还是回
//到第一步,如果第一步的tryAcquire返回false说明加锁失败,取反后,继续跑下面的逻辑
//即下面两个函数 acquireQueued(addWaiter(Node.EXCLUSIVE), arg));selfInterrupt();
//我们要关注的有三个函数:
//================================
//函数一 此方法主要封装了以下操作获取锁的线程Node节点会被移出队列,获取失败则会进入阻塞
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前驱节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//如果前驱节点是头节点,并且自己已经获得锁
//就把当前节点设置为头节点,因为前驱节点已经获得了锁,所以前驱节点不用再留在队列
setHead(node);
p.next = null; // help GC
return interrupted;
}
//如果前驱节点不是头节点或者没有获得锁,shouldParkAfterFailedAcquire用于判断当前线程是否需要被阻塞,parkAndCheckInterrupt用于阻塞线程并且检测线程是否被中断
if (shouldParkAfterFailedAcquire(p, node) &&//这个函数会在下面单独贴出来
parkAndCheckInterrupt())//这个函数会在下面单独贴出来
interrupted = true;//记录在这个方法执行过程中,是否被中断
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
//函数二 将当前线程以mode的方式(EXCLUSIVE或者SHARED)构成新节点并入队,返回这个新节点
private Node addWaiter(Node mode) {
Node node = new Node(mode);//创建一个新的节点A
for (;;) {
Node oldTail = tail;//记录队尾节点
if (oldTail != null) {
U.putObject(node, Node.PREV, oldTail);//插入节点至队列尾部
if (compareAndSetTail(oldTail, node)) {//利用cas操作将队尾指针指向新节点
oldTail.next = node;//将旧的队尾节点指向新节点A
return node;
}
} else { //tail为null,说明队列还没初始化
//创建一个节点,作为同步队列的第一个元素,head、tail都指向它,然后进入下一个循环
initializeSyncQueue();
}
}
}
//函数三
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
shouldParkAfterFailedAcquire函数和parkAndCheckInterrupt函数如下
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱节点的等待状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //SINGAL表示后继结点处于等待状态,当前节点释放了锁或者被取消,就会通知后续点去运行,作为后继节点,node直接返回true,表示需要被阻塞
return true;
if (ws > 0) {
do {
//前驱节点被取消了,需要从队列中移除,并且循环找到下一个不是取消状态的节点
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//通过CAS将前驱节点设置为SINGAL
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞当前线程
return Thread.interrupted();//检测该线程是否被中断,该方法会清除标志位
}
到此加锁的过程已基本结束,接下来从头回看下: 1、首先代码里面调用 lock.lock();—》2、然后就调用到NonfairSync的lock方法。这个方法会先尝试去获取锁 把state设置为1。如果失败则调用acquire方法 ----》3、acquire方法首先会调用tryAcquire方法进行加锁,如果加锁失败调用acquireQueued ----》4、acquireQueued方法的一个参数调用了addWaiter会把当前线程封装成node,并加到队列尾部,并返回当前node节点给acquireQueued----》5、acquireQueued方法会尝试再去获取锁,成功获取锁的线程Node节点会被移出队列,获取失败则会进入阻塞。看完加锁的逻辑,接下来看看解锁的代码
//ReentrantLock里面的unlock方法,可以看到调用了sync的release方法
public void unlock() {
sync.release(1);
}
// 这个release方法,调用的是aqs里面的release
public final boolean release(int arg) {
if (tryRelease(arg)) {//调用1 尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
//头节点已经释放,唤醒后续节点
unparkSuccessor(h);//调用2
return true;
}
return false;
}
//调用1
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//state减1,因为线程可能重入这个锁不止一次,所以state的值可能已经大于1了
if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不等于拥有这个锁的线程,就抛出异常
throw new IllegalMonitorStateException();
//是否完全的释放了锁(可重入性)
boolean free = false;
if (c == 0) {
//完全释放了锁
free = true;
//独占锁的持有者设置为null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//调用2
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;//获取头节点的waitStatus状态
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);//将头节点的waitStatus状态用CAS操作尝试设置为0
Node s = node.next;//将头节点的下一个节点赋值给s
//下面这段代码的逻辑,就是不断循环直到获取到头节点后第一个可用节点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)//这里之所以要从末尾开始遍历,因为prev是可靠的,next是不可靠的,这个跟插入的代码有关系,详情看enq函数。
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);//对获取到的可用节点进行唤醒
}
//enq函数如下,这个函数的作用就是插入节点
private Node enq(Node node) {
for (;;) {
Node oldTail = tail;//保存老队尾节点
if (oldTail != null) {
U.putObject(node, Node.PREV, oldTail);//将新插入的节点的prev指向老队尾节点
if (compareAndSetTail(oldTail, node)) {//将队尾节点更新为新插入节点
//oldTail.next = node 如果这个赋值操作没执行到,就会暂时出现oldTail.next = null的虚假情况,所以next节点是不可靠的,所以需要从末尾遍历,使用prev节点
oldTail.next = node;//老队尾节点的next指向新插入节点
return oldTail;
}
} else {
initializeSyncQueue();
}
}
}