Java显式锁学习总结之四:ReentrantLock源码分析
2017-03-01 10:25
666 查看
达人科技 2017-02-28 12:45
除了支持可中断获取锁、超时获取锁、非阻塞获取锁这些显示锁的常见功能外,ReentrantLock还支持公平锁(synchronized只支持非公平锁)。
下面分析源码时将聚焦重入和公平这两个功能点的实现。
Sync类是AQS的子类,而NonfairSync和FairSync是Sync的子类。
公平锁和非公平锁的区别就在于获取锁时候的逻辑略有不同,其他操作都是一样的,因此公用的操作都放在Sync类里,NonfairSync和FairSync里只是实现自己的tryAcquire(int acquires)方法。
AQS里的state在重入锁里代表线程重入的次数,state=1代表重入锁当前已被某个线程独占,这个线程每重入一次,state++。因为state是int型变量,因此重入锁可以重入的最大次数是2^31-1。
我们可以发现,这段代码与上面的 nonfairTryAcquire方法就只多了一句代码,而就是这一句代码就实现了公平锁。hasQueuedPredecessors方法判断同步队列中是否有更早开始等待锁的线程。如果有,则tryAcquire方法直接返回false让当前线程进入同步队列排队。
看一下ReentrantLock类的tryLock方法的实现:
可以看出,不管是公平锁还是非公平锁,调用的都是 nonfairTryAcquire 方法。
为什么这么实现呢?我们可以想一下tryLock 的语义,tryLock 要实现的效果是尝试获取一次锁,如果获取失败不阻塞而是直接返回false。如果在公平锁模式下严格按照公平锁的定义来实现这个方法,那么当同步队列中有其他线程等待的时候,tryLock都不可能获取到锁,只能返回false。
而事实上,当我们调用tryLock的时候,很多时候应该都是希望尽可能的成功的,而此时要不要让tryLock的线程严格排队,其实不是那么重要,因此公平锁下tryLock方法在获取锁时使用非公平获取模式,即可以插队。
那么如果我们在公平锁模式下就希望tryLock方法获取锁严格排队呢?可以用tryLock(0, TimeUnit.SECONDS),这个方法等效于一个严格排队的tryLock方法,之所以等效,是因为tryLock(long timeout, TimeUnit unit)的实现是区分公平锁和非公平锁的,在公平锁的模式下,获取锁的操作是严格按同步队列排队等待的。
那么如果我们在公平锁模式下希望 tryLock(long timeout, TimeUnit unit) 不严格排队,表现的像一个支持超时的tryLock呢?也是有办法的:
可以这样组合一下,左边的tryLock有机会插队获取一次锁,如果没获取到,在用tryLock(timeout, unit)做一次可超时的同步队列排队。
概述
ReentrantLock,即重入锁,是一个和synchronized关键字等价的,支持线程重入的互斥锁。只是在synchronized已有功能基础上添加了一些扩展功能。除了支持可中断获取锁、超时获取锁、非阻塞获取锁这些显示锁的常见功能外,ReentrantLock还支持公平锁(synchronized只支持非公平锁)。
下面分析源码时将聚焦重入和公平这两个功能点的实现。
结构总览
重入锁的大体结构如下:public class ReentrantLock implements Lock, java.io.Serializable { abstract static class Sync extends AbstractQueuedSynchronizer {} static final class NonfairSync extends Sync {} static final class FairSync extends Sync {} }
Sync类是AQS的子类,而NonfairSync和FairSync是Sync的子类。
公平锁和非公平锁的区别就在于获取锁时候的逻辑略有不同,其他操作都是一样的,因此公用的操作都放在Sync类里,NonfairSync和FairSync里只是实现自己的tryAcquire(int acquires)方法。
AQS里的state在重入锁里代表线程重入的次数,state=1代表重入锁当前已被某个线程独占,这个线程每重入一次,state++。因为state是int型变量,因此重入锁可以重入的最大次数是2^31-1。
重入实现
重入实现其实上边已经提到了,就是利用state状态表示重入次数,我们以非公平锁的代码为例看一下,下面是Sync类里的 nonfairTryAcquire(int acquires)方法 (上面我们说过,Sync类里是存放NonfairSync与FairSync的公用代码,那么这个nonfairTryAcquire方法为什么放到Sync里呢?我们后面会解释,不要急:)final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread; int c = getState;
公平实现
公平锁的实现非常简单,其实就是一句代码,我们看一下FairSync类里的 tryAcquire(int acquires) 方法: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; }
public final boolean hasQueuedPredecessors { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread); }
我们可以发现,这段代码与上面的 nonfairTryAcquire方法就只多了一句代码,而就是这一句代码就实现了公平锁。hasQueuedPredecessors方法判断同步队列中是否有更早开始等待锁的线程。如果有,则tryAcquire方法直接返回false让当前线程进入同步队列排队。
特殊的tryLock
之前我们在将重入实现的时候说到,Sync类里有个诡异的nonfairTryAcquire方法,听名字是和非公平锁相关的,按道理应该放到NonfairSync类啊。之所以有这么别扭的设计是为了服务tryLock方法。看一下ReentrantLock类的tryLock方法的实现:
public boolean tryLock { return sync.nonfairTryAcquire(1); }
可以看出,不管是公平锁还是非公平锁,调用的都是 nonfairTryAcquire 方法。
为什么这么实现呢?我们可以想一下tryLock 的语义,tryLock 要实现的效果是尝试获取一次锁,如果获取失败不阻塞而是直接返回false。如果在公平锁模式下严格按照公平锁的定义来实现这个方法,那么当同步队列中有其他线程等待的时候,tryLock都不可能获取到锁,只能返回false。
而事实上,当我们调用tryLock的时候,很多时候应该都是希望尽可能的成功的,而此时要不要让tryLock的线程严格排队,其实不是那么重要,因此公平锁下tryLock方法在获取锁时使用非公平获取模式,即可以插队。
那么如果我们在公平锁模式下就希望tryLock方法获取锁严格排队呢?可以用tryLock(0, TimeUnit.SECONDS),这个方法等效于一个严格排队的tryLock方法,之所以等效,是因为tryLock(long timeout, TimeUnit unit)的实现是区分公平锁和非公平锁的,在公平锁的模式下,获取锁的操作是严格按同步队列排队等待的。
那么如果我们在公平锁模式下希望 tryLock(long timeout, TimeUnit unit) 不严格排队,表现的像一个支持超时的tryLock呢?也是有办法的:
if (lock.tryLock || lock.tryLock(timeout, unit)) { ... }
可以这样组合一下,左边的tryLock有机会插队获取一次锁,如果没获取到,在用tryLock(timeout, unit)做一次可超时的同步队列排队。
总结
有了前面对AQS的理解基础,现在再来看同步组件的实现,就如果快刀切西瓜!所以说,Doug Lea对AQS的设计真的非常巧妙,ReentrantLock没有用多少代码,就实现了一个加强版的synchronizer。相关文章推荐
- Java显式锁学习总结之四:ReentrantLock源码分析
- Java显式锁学习总结之四:ReentrantLock源码分析
- 并发编程学习总结(六) :java 显式锁ReentrantLock使用详解之测试锁与超时
- 并发编程学习总结(四) :java 显式锁ReentrantLock使用详解之lock()\unlock() 加锁与释放锁
- 并发编程学习总结(五) :java 显式锁ReentrantLock使用详解之条件对象(2)
- Java显式锁学习总结之四:ReentrantLock源码分析
- Java并发Concurrent包的锁(三)——ReentrantLock源码分析
- java.util.concurrent源码分析(三)ReentrantLock实现
- java ReentrantLock源码 分析 妥妥的
- java源码分析:重入锁ReentrantLock
- Java并发系列之ReentrantLock源码分析
- 【Java8源码分析】locks包-ReentrantReadWriteLock
- Java并发编程 ReentrantLock 源码分析
- jdk 源码分析(9)java ReentrantReadWriteLock分析
- java并发锁ReentrantLock源码分析一 可重入支持中断锁的实现原理
- Java三种锁机制初步分析总结(Synchronized Lock(ReentrantLock) Semaphore Atomic)
- Java并发包源码学习之AQS框架(二)CLH lock queue和自旋锁
- Java 集合源码分析及总结
- java ReentrantLock与synchronized详细分析与例子详解
- Java并发包源码学习之AQS框架(三)LockSupport和interrupt