一、ReentrantLock 类介绍

我觉得学习Java源码一定要首先读读源码的注释文档。

官方注释文档解释到,该类作为一个提供可重入功能的锁,实现了Lock接口,提供了和synchronized类似的功能,并且提供了额外的功能以实现同步。

二、ReentrantLock中的相关属性和方法

属性含义Sync syncSync为ReentrantLock中的静态内部类,继承了AbstractQueuedSynchronized类,其中AbstractQueueSynchronized类提供了实现可重入锁最基本的功能构造函数含义public ReentrantLock()默认构造函数,内部构造出NonfairSync类,该类为Sync的子类,也是ReentrantLock中的内部类。主要实现非公平锁public ReentrantLock(boolean fair)传入参数指示创建公平锁实现类(FairSync)还是非公平锁(NonfairSync)相关重要方法含义public lock()内部调用sync.lock(),当前线程获取锁的方法,如果已经有线程获取了同一个ReentrantLock的锁,那么其他线程就会挂起等待,如果同一个线程试图再次获取锁,那么会将当前的state加1,后续会说到实现。public boolean tryLock()内部调用sync.nonfairTryAcquire(1),如果可以获取锁会设置状态并获取锁然后返回true,如果不能获取锁,那么会返回false。public void unlock()内部调用sync.release(1);,如果当前线程持有锁,那么将states相应递减,如果state减为0了,那么释放锁。如果当前线程没有持有锁,并且执行了该方法,那么会抛出IllegalMonitorStateException异常。

先说明这几个方法,下面将从NonfairSync出发说明lock()和unlock()的主要实现。

三、ReentrantLock内部类sync、NonfairSync的实现。

调用ReentrantLock的默认构造器获取一个可重入锁的对象,其内部其实是执行了sync = new NonfairSync();构造出一个非公平锁对象,那么NonfairSync类是怎么写的呢,下面看其源代码:

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);

}

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

}

这段源码可以看出来,其主要提供lock和tryAcquire方法。但是都是调用父类的方法。那么Sync怎么定义的呢,看看其源码:

abstract static class Sync extends AbstractQueuedSynchronizer {

private static final long serialVersionUID = -5179523762034025860L;

/**

* Performs {@link Lock#lock}. The main reason for subclassing

* is to allow fast path for nonfair version.

*/

abstract void lock();

/**

* Performs non-fair tryLock. tryAcquire is implemented in

* subclasses, but both need nonfair try for trylock method.

*/

final boolean nonfairTryAcquire(int acquires) {

。。。

}

protected final boolean tryRelease(int releases) {

。。。

}

}

部分代码被我删了,可以自行去看,我们可以看到其实Sync只是定义了规范供子类拓展,实现方法都在AbstractQueuedSynchronizer中。该类才是重中之重。

下面从获取锁开始一步步debug看看代码到底如何走的.有如下启动代码:

public class TestReentrantlock {

private static final ReentrantLock lock = new ReentrantLock(false);

static class T implements Runnable{

private static long i = 0;

public void run() {

try {

setAndGet(Thread.currentThread().getId());

}

catch (Exception e){

e.printStackTrace();

}

}

void setAndGet(long a) throws Exception{

lock.lock();

try {

System.out.println("thread-" + Thread.currentThread().getName());

System.out.println("beafore i : " + i);

i = a;

System.out.println("after i : " + i);

}

catch (Exception e){

e.printStackTrace();

}

finally {

Thread.sleep(1000);

lock.unlock();

}

}

}

public static void main(String[] args) {

Thread t1 = new Thread(new T(),"t1-thread");

Thread t2 = new Thread(new T(), "t2-thread");

t1.start();

t2.start();

}

}

我这边启动两个线程,看看到底如何获取锁和释放锁的。

我用的是idea debug的,记得在断点处设置debug模式,如下:

这边可以选择具体要调试的线程,可以随便选择一个,现在两个都停在lock.lock()处了。我选择t1-thread进行调试把获取锁的实现过程走一遍,相关的方法如下图所示。

如上所示,在没有其他线程获取锁的状态下,走的相关方法。可以看出比较简单,其中unsafe.compareAndSwapInt(this,stateOffset,expect,update),就是著名的java提供的原生的原子操作。该方法的意思是:获取参数1,这里也就是this的偏移量(参数二),这里也就是stateOffset的内存存储的值,如果expect的值和该值相同,那么就将该值设置为update。这里需要设置的值是state,该值就表示当前锁的状态,在AbstractQueuedSynchronizer(AQS)中定义,其定义如下:private volatile int state; 可以看出,该值用volatile声明了,代表该值的变化对其他线程是可见的。

该流程可以简单的描述为下面的方式:通过原子操作将state的值设置为1,代表当前线程获取了一个锁,然后设置线程为当前线程。

那么此时t1-thread已经获取了当前锁,此时用t2-thread再去获取锁是什么样的一个流程呢?下面开始调试:

其流程如下:

下面分析每一步的代码:

/*NonfairSync.lock()*/

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

这段代码首先判断state的值是不是0,因为现在有一个线程持有锁了,所以进入,acquire(1)。

/*分别执行了tryAcquire(1)再次尝试获取锁,如果还不能获取锁,调用addWaiter(Node.EXCLUSIVE)将当前线程添加进等待链表,acquireQueued会将当前线程再次申请锁,如果还未成功,则将当前线程挂起*/

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

/*addWaiter源码,首先创建当前线程的一个节点Node,tail存储的链表的当前节点,如果当前节点已经存在了,直接将线程节点插入到链表的下一个节点,并将当前节点置为线程节点,并返回节点

如果链表中还没有节点,那么调用enq(node)

*/

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;

}

/*用一个for循环创建链表,并返回tail头节点*/

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;

}

}

}

}

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)) { //判断p是否为head节点,再次尝试获取锁

setHead(node);

p.next = null; // help GC 释放线程节点

failed = false;

return interrupted;

}

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt()) //shoudParkAfterFailedAcquire设置p节点的waitstatus为-1,parkAndCheckInterrupt()设置当前线程挂起。等待获取锁的线程释放锁资源。

interrupted = true;

}

} finally {

if (failed)

cancelAcquire(node);

}

}

那么挂起的线程什么时候恢复并且去再次获取锁呢?

当前持有锁的线程释放的时候,看下释放的调用过程:

具体的代码可以跟下源代码。