我觉得学习Java源码一定要首先读读源码的注释文档。
官方注释文档解释到,该类作为一个提供可重入功能的锁,实现了Lock接口,提供了和synchronized类似的功能,并且提供了额外的功能以实现同步。
属性 | 含义 |
Sync sync | Sync为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 = 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); } }
那么挂起的线程什么时候恢复并且去再次获取锁呢?
当前持有锁的线程释放的时候,看下释放的调用过程:
具体的代码可以跟下源代码。