前言
前几天学习了解了AQS也叫列同步器AbtstractQueuedSynchronizer的相关源码,今天静下心学习了看了看重入锁的相关源码,在看这篇文章之前希望你可以对ASQ有一定的了解,因为AQS是实现重入锁,读写锁等等的关键,这是自己的学习AQS的链接Java并发AQS队列同步器源码学习笔记,如果不想看的话可以看这篇Java并发之AQS详解,写的很好。在这篇文章中我主要讲一下对重入锁主要方法源码的理解。
接下来我们直接看源码吧,首先我们要理清ReentrantLock类的结构,看它的里面有哪些内部类,继承了谁,实现了什么方法,希望你在看这篇文章的时候可以简单的写这几行代码一步一步点进去,看看每一步都调用了什么方法。
public class MyThread {
public static void main(String[] args){
ReentrantLock lock = new ReentrantLock();
lock.tryLock();
lock.unlock();
}
}
创建对象首先要调用构造方法,我们点进ReentrantLock。
构造方法
可以看出重入锁ReentrantLock提供了两种构造器,默认的为非公平方法。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
那构造器中的new的NonfairSync()对象或者fairSync()对象又是什么呢?我们接着看源码,顺着点进去NonfairSync(),看看这是个什么东东?fairSync()也是大同小异。
NonfairSync
可以看出NonfairSyn是重入锁ReentrantLock的静态内部类继承于Sync,至于里面的两个方法lock()、tryAcquire()后面会讲到,那么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);
}
}
Sync
可以看出Sync也是重入锁的内部静态类,而且还是抽象的,继承了AbstractQueuedSynchronizer,也就是AQS的同步器,所以说最好对AQS有点了解最好。我们接着看,对于Sync类的代码我只保留了3个重要方法,而且可以观察到上面提到的NonfairSync中的lock方法就是实现Sync的lock方法。而NonfairSync中的tryAcquire()直接调用了它爹Sync的nonfairTryAcquire,是不是很真实。。。
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
...方法体省略
}
protected final boolean tryRelease(int releases) {
...方法体省略
}
}
好了现在起码我们将重入锁中的结构理的差不多了,接着我们看一下它的tryLock()方法,我们继续一步一步点进去看。
tryLock()
这里sync是Sync的实例?明显是不对的,刚才不是说了吗,Sync是ReentrantLock的静态抽象内部类,不可能实例化实例化对象的,其实这里只是一个句柄引用,使用父类来引用子类实例对象,你回头去看一下刚开始初始化ReentrantLock的构造器方法就知道了。那既然知道sync是NonfairSync的实例,那么这就显而易见了,重入锁ReentrantLock的tryLock()是调用了NonfairSync的nonfairTryAcquire(),可是你可能发现这又不对了,这NonfairSync没有这个方法啊,但是你一点发现是它爹Sync的方法,也就是实例化的对象是NonfairSync,句柄是它爹,用的是它爹的方法,一切还是没啥毛病。
private final Sync sync;
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
终于到了tryAcquire()方法的核心实现了nonfairTryAcquire(int acquires)方法了。
nonfairTryAcquire(int acquires)
注释写的很详细了。我说一下核心思想,重入锁,顾名思义就是支持重入的锁,就是表示该锁能支持一个线程对资源进行重入加锁。那么这个方法就是支持重入的实现。可以看出核心就是维护一个private volatile int state变量,这是同步器的同步状态。在线程不持有锁的情况下是0,你看就是调用一次tryLock,就是调用一次nonfairTryAcquire(1),也就是当前线程在持有锁的情况下将state+1。
// 非公平方法获得锁
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 火获得当前线程状态
int c = getState();
// 判断当前线程状态是否为0
if (c == 0) {
// 为0就表示没有持有锁,采用CAS方法将线程状态设置为acquires,也就是1,这是第一次加锁获取资源
if (compareAndSetState(0, acquires)) {
// 如果设置成功,将当前线程设置为独占式访问。
setExclusiveOwnerThread(current);
return true;
}
}
// 否则判断当前线程是否占有锁
else if (current == getExclusiveOwnerThread()) {
// 将当前线程状态+ acquires,也就是+1,核心就在这儿
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 否则更新同步器状态,返回true
setState(nextc);
return true;
}
return false;
}
接着我们看非公平方式释放锁unlock()
unlock()
可以看出调用了release()方法,而点进去可以看到release()方法时AbstractQueuedSynchronizer的方法,也就是Sync它爹的方法。所以说必须得了解AQS队列同步器,再次附上这位大佬的文章Java并发之AQS详解。
public void unlock() {
sync.release(1);
}
我们接着点
release(int arg)
这里面涉及到tryRelease(arg)和unparkSuccessor(Node node)两个方法,第二个方法属于AQS的方法,具体请拜读上面大佬的文章。我们来说说第一个方法。
public final boolean release(int arg) {
// 如果成功释放锁
if (tryRelease(arg)) {
Node h = head;// 找到头结点
if (h != null && h.waitStatus != 0)
// 唤醒等待队列里的下一个线程
unparkSuccessor(h);
return true;
}
return false;
}
我们接着点tryRelease()方法,这是锁释放的关键。
tryRelease(int releases)
注释很详细了,我说一下核心思想,既然上面已经提到可以多次获取锁,每次同步器状态state+1,那么现在释放锁是不是也得每调用一次unlock()也得让同步器状态-1呢?确实如此,当state=0时,表示锁已经成功释放,接着执行release()方法中的后续进程,即唤醒等待队列中的下一个线程。
//独占式的获取同步状态
protected final boolean tryRelease(int releases) {
// 将当前线程状态- releases
int c = getState() - releases;
// 判断当前线程是否是setExclusiveOwnerThread方法最后一次设置的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否成功释放标志位
boolean free = false;
// 判断线程状态是否为0,为0表示锁已经被成功释放
if (c == 0) {
free = true;// 标志位置为true
// 独占式访问线程设置为null
setExclusiveOwnerThread(null);
}
// 设置线程状态
setState(c);
// 返回释放锁是否成功
return free;
}
到这里ReentrantLock的trylock()和unlock()已经讲完了,这里讲的是ReentrantLock默认构造器的两个方法,也就是非公平的方式获取锁,公平的方法获取锁的trylock()方法与非公平的方式相比,只有tryAcquire()中下面这点不同,它会判断当前线程所在节点是否有前驱节点,如果有,只有等待前驱节点获得并且释放锁之后才能获得。
非公平方式的tryAcquire(int acquires)
// 公平方式获得锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors()判断同步队列中当前节点是否有前驱节点,如果有返回true
// 表示有比当前线程更早请求获得锁,需要前驱线程获得并释放锁后才能获得锁。
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;
}
至于两种方式的lock()方法,都是调用父类AQS的acquire()方法来实现的,具体参考AQS的源码解读Java并发AQS队列同步器源码学习笔记,这里提一下大致流程。这里说一下非公平方式的lock()方法,公平方式的大同小异。
final void lock() {
// 在同步器状态为0时,采用CAS的方法尝试设置同步器状态为1
if (compareAndSetState(0, 1))
// 如果成功,将当前线程设置为独占访问状态
setExclusiveOwnerThread(Thread.currentThread());
else
// 走到这里就说明已经不是第一次获得锁了,调用acquire()方法
acquire(1);
}
我们接着看acquire()方法
acquire(int arg)
这个方法是ReentrantLock内部Sync的爹AQS的方法,这个方法比较复杂,详细还是建议您去看AQS,这里只提一下tryAcquire(arg)方法,因为后面的方法在AQS里已经实现,只有tryAcquire(arg)这个方法留给子类让子类来实现,结果它的儿子Sync直接就没有重写,还是抽象的,而Sync将重写的这个方法的任务交给了它的两个儿子,NonfairSync和FairSync,这两个内部类重写了这个方法。而这个方法在前面已经介绍过了,就不在赘述了。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
总结
重入锁的实现的思想很简单,就是维护一个volatile int state的同步器状态,在线程获取锁之后可以多次获得锁,每重入一次,状态+1,而在释放锁的时候每释放一次同步状态就-1,当减到0时,就表示锁已经成功释放。至于非公平方式,就是在获得锁的时候判断当前节点之前是否有前驱节点,如果有,只有等待前驱节点获得并且释放锁之后才能获得。释放锁的方式公平和非公平方式都是一样的。
参考文献
[1] Java并发编程艺术