Java 源码分析-ReentrantLock
对AbstractQueuedSynchronizer分析完毕,现在来对ReentrantLock类来进行分析。在阅读本文之前,建议对AbstractQueuedSynchronizer有所了解,可以借鉴楼主的Java 源码分析-AbstractQueuedSynchronizer。
只要对AbstractQueuedSynchronizer比较熟悉,那么理解ReentrantLock就不是很难的事。因为ReentrantLock的内部就是利用AbstractQueuedSynchronizer来实现的。
本文参考资料:
1.方腾飞、魏鹏、程晓明的《Java 并发编程的艺术》
2.楼主本人的Java 源码分析-AbstractQueuedSynchronizer
1.ReentrantLock的介绍
ReentrantLock称为重入锁,顾名思义,就是支持重入的锁,它表示锁能够支持一个线程对资源进行重复的加锁。
ReentrantLock相较于其他的锁,除了支持重入之外,还在锁的公平上有所区别。普通的锁可能就是单纯使用同步队列来实现锁的排队,而ReentrantLock在锁的定义上分为了公平锁和非非公平锁。
也就是说,对于ReentrantLock,我们需要关注两个地方,一个是锁的重入,一个是公平性。
(1).锁重入的说明
楼主在Java 源码分析-AbstractQueuedSynchronizer里面定义了一个Mutex类,如果现在有一个这样的场景:当一个线程调用Mutex的lock方法获取锁之后,如果再次调用lock的话,则该线程将会被自己阻塞,原因是Mutex在实现tryAcquire方法时没有考虑到占有锁的线程再次获取锁的场景,而在tryAcquire方法里面返回了false,导致该线程被阻塞。简单地说,Mutex是一个不支持重入的锁。
而系统的synchronized关键字隐式的支持重入,在实际的开发中,我们也遇得到一个被synchronized修饰的方法内部调用了另一个被synchronized修饰的方法的情况。
(2).锁公平性的说明
如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁就是公平的,反之,这个锁就是不公平的。公平的获取锁,也就是说等待时间最长的最先获取锁。ReentrantLock提供了一个构造函数,能够控制所是否是公平的。
2.锁重入的实现原理
大家不要被我的题目唬住了,什么实现原理都是非常简单的。
关于锁的重入,我们必须考虑到两个问题:
1.线程再次获取锁。锁需要识别获取的线程是否为当前占据锁的线程,如果是的话,可以再次获取锁,反之则不能获取锁。
2.锁的最终释放。线程重复n次获取了锁,随后在第n次释放了锁后,其他线程才能够获取到该锁。锁的最终释放要求对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放了,其他的线程可以进行进行锁的争夺了。
(1).锁的获取
这里使用非公平性实现的为例,来解释锁获取的实现。
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;
}
从这段代码里面,我们可以看出来,通过判断当前线程是否是获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求的话,则将同步状态值进行增加并且返回true,表示获取成功。
成功锁的线程再次获取锁,只是增加了同步状态值,这也就是要求ReentrantLock在释放同步状态时时减少同步状态值。
(2).锁的释放
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;
}
如果该锁被获取了n次,那么前 n - 1次tryRelease方法必须返回false,而只有同步状态全部释放了,才能返回true。可以看出来,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并且返回true,表示释放成功。
3.锁公平性和非公平性的区别和实现原理
公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。
下面我贴出FairSync的tryAcquire代码,注意跟nonfairTryAcquire方法做比较。
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;
}
tryAcquire方法与nonfairTryAcquire方法比较,唯一的区别在于tryAcquire方法多了一个hasQueuedPredecessors的判断条件。这个判断条件作用是,加入同步队列中当前节点是否有前驱节点,如果有的话,表示有线程比当前线程更早地请求获取锁,因此需要等待前驱获取锁并且释放锁之后才能继续获取锁。
上一篇: java 源码分析 ---Boolean
下一篇: Linux挂载Windows共享文件夹