
ReentrantLock实现原理
前言
建议和上一篇分享结合着看:深入理解AbstractQueuedSynchronizer
先举个例子,下面程序输出始终是5000,可以用ReentrantLock来保证线程安全
源码
基于jdk1.8.0_20,先看UML图
Lock接口中定义了对锁的各种操作
ReentrantLock在AQS基础上,又扩展出来非公平锁和公平锁,那么它现在有4种功能,各种操作分别在NonfairSync和FairSync这两个静态内部类中实现
- 响应中断的非公平锁
- 不响应中断的非公平锁
- 响应中断的公平锁
- 不响应中断的公平锁
AQS有一个state变量,在不同子类中有不同的含义,在ReentrantLock中表示锁的状态
- status的值表示加锁的次数,无锁时值为0,第一次加锁将status设置为1,由于ReentrantLock是可重入锁,当持有锁的线程是当前线程时,即可加锁,加锁一次,将status的值加1
- 每解锁一次将status的个数减1,当stauts的值为0,其他线程可以获得锁
ReentrantLock只有一个成员变量Sync,Sync相当于一个代理类,具体的实现在子类中定义
ReentrantLock类有两个构造函数,默认是非公平锁。当传入的参数是true时为公平锁,是false为非公平锁
不响应中断的非公平锁
来个小插曲,尝试非阻塞的获取锁直接调用的就是nonfairTryAcquire方法
接着看释放锁,公平锁和非公平锁用的都是这个方法
不响应中断的公平锁
这几步都一样
从这开始不一样了
如果head=tail,则表示FIFO队列为空,如刚开始head和tail都为null,返回false 如果head!=tail,并且head的next为空时,则FIFO队列不为空
有两种情况会导致h的next为空
- 当前线程进入hasQueuedPredecessors的同时,另一个线程已经更改了tail,但还没有将head的next指向tail
- 当前线程将head赋给h后,head被另一个线程移除队列,导致h的next为空,这种情况说明锁已经被占用
后记
最后再说一下公平锁和非公平锁,举个例子
执行结果有时如下
可以看到开始运行的顺序和获得锁的顺序是不一致的
将lock成员变量改为如下
执行结果有时如下
可以看到开始运行的顺序和获得锁的顺序是一致的,但是这并不是绝对的,假设线程B调用tryAcquire失败后,并在调用addWaiter之前,线程A释放了锁,且线程C判断到锁空闲,进入hasQueuedPredecessors返回false(等待队列为空),最终C比B先获取到锁,因此公平锁并不是绝对公平的
非公平的锁的效率高于公平锁的效率,是因为在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟,假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。
文章转载自公众号:Java识堂
