并发编程 —— 源码分析公平锁和非公平锁
2018-05-19 23:13
351 查看
前言
ReentrantLock 提供了公平锁和非公平锁,只需要在构造方法中使用一个
boolean参数即可。默认非公平锁。
今天从源码层面看看区别和具体实现。
1. 类 UML 图
ReentrantLock内部有一个抽象类
Sync,继承了 AQS。
而公平锁的实现就是
FairSync,非公平锁的实现就是
NodFairSync。
两把锁的区别在于
lock方法的实现。
2. 公平锁 lock 方法实现
final void lock() { acquire(1); }
调用的是 AQS 的
acquire方法,熟悉 AQS 的同学都知道,AQS 会回调子类的
tryAcquire方法,看看公平锁的
tryAcquire实现。
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; }
说下逻辑:
- 获取 state 变量,如果是 0,说明锁可以获取。
- 判断 AQS 队列中是否有等待的线程,如果没有,就使用 CAS 尝试获取。获取成功后,将 CLH 的持有线程修改为当前线程。
- 重入锁逻辑。
- 如果失败,返回 false, AQS 会将这个线程放进队列,并挂起。
注意上面的第二步:判断 AQS 队列中是否有等待的线程。
这就是公平的体现。
再看看非公平锁的区别。
3. 非公平锁 lock 方法实现
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
lock 方法就不同了,很冲动的一个方法,直接使用 CAS 获取锁,如果成功了,就设置锁的持有线程为自己。很快速。所以默认使用了非公平锁。
如果失败了,就调用 AQS 的
acquire方法。当然,我们看的还是
tryAcquire方法,在上面的代码中,
tryAcquire方法调用了父类
Sync的
nonfairTryAcquire,为什么在父类中呢?
在
ReentrantLock的
tryLock方法中,也调用了该方法。因为这个方法是快速返回的。该方法不会让等待时间久的线程获取锁。符合
tryLock的设计。
方法实现如下:
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; }
该方法相比较公平锁的
tryAcquire方法,少了一步判断 AQS 队列中是否有等待的线程的操作。
他要做的就是直接抢锁,不让给队列里那些等待时间长的。
抢不到再进入队列。等待他的前置节点唤醒他。这个过程是公平的。
4. 总结
ReentrantLock中的公平锁和非公平锁的区别就在于:调用
lock方法获取锁的时候
要不要判断 AQS 队列中是否有等待的线程,公平锁为了让每一个线程都均衡的使用锁,就需要判断,如果有,让给他,非公平锁很霸道,不让不让就不让。
但如果失败了,进入队列了,进会按照 AQS 的逻辑来,整体顺序就是公平的。
还有个注意的地方就是:
ReentrantLock的
tryLock(无超时机制)方法使用的非公平策略。符合他的设计。
而
tryLock(long timeout, TimeUnit unit)方法则会根据 Sync 的具体实现来调用。不会冲动的调用
nonfairTryAcquire方法。
相关文章推荐
- AbstractQueuedSynchronizer源码分析一(独占锁部分,公平非公平)
- 并发编程之——写锁源码分析
- ReetrantLock源码解析(一):获得公平锁和非公平锁lock()
- 深入剖析ReentrantLock公平锁与非公平锁源码实现
- java并发编程--AbstractQueuedSynchronizer公平锁和非公平锁分析(三)
- 源码角度来分析ReentrantLock是怎么利用AQS来实现公平锁,和非公平锁
- 并发编程源码分析一之Log接口
- 【Java8源码分析】locks包-ReentrantReadWriteLock
- Java-TreeSet源码分析及示例
- [原创] jQuery源码分析-04 选择器-Sizzle-设计思路
- 谷歌浏览器的源码分析(16)
- [Abp 源码分析]一、Abp 框架启动流程分析
- dubbo源码分析-consumer端4-MockClusterInvoker
- 从源码分析Tomcat如何处理Http请求
- SQLmap源码分析之框架初始化(一)
- 数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 解析(六)之删除SQL
- [Java] LinkedHashMap 源码简要分析
- wifi设计原理(源码分析)
- LDD3源码分析之poll分析
- 简化高仿以及源码分析Android 5.0的CardView