理解ReentrantLock的公平锁和非公平锁
学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
这时,我有了一个疑问,AQS的同步队列是FIFO的,就是先来排队的先走。那怎么实现非公平锁呢?查阅了一些资料,总算知道了。
首先从公平锁开始看起。
ReentrantLock 的公平锁
ReentrantLock 默认采用非公平锁,除非在构造方法中传入参数 true 。
//默认
public ReentrantLock() {
sync = new NonfairSync();
}
//传入true or false
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁的 lock 方法:
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
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;
}
}
我们可以看到,在注释1的位置,有个!hasQueuedPredecessors()条件,意思是说当前同步队列没有前驱节点(也就是没有线程在等待)时才会去compareAndSetState(0, acquires)使用CAS修改同步状态变量。所以就实现了公平锁,根据线程发出请求的顺序获取锁。
非公平锁的lock方法
static final class NonfairSync extends Sync {
final void lock() {
// 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//3.这里也是直接CAS,没有判断前面是否还有节点。
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;
}
非公平锁的实现在刚进入lock方法时会直接使用一次CAS去尝试获取锁,不成功才会到acquire方法中,如注释2。而在nonfairTryAcquire方法中并没有判断是否有前驱节点在等待,直接CAS尝试获取锁,如注释3。由此实现了非公平锁。
总结
非公平锁和公平锁的两处不同:
非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
作者:小北觅
链接:https://www.jianshu.com/p/2ada27eee90b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
理解ReentrantLock的公平锁和非公平锁的更多相关文章
- java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁
公平锁与非公平锁 ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得 ...
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...
- ReentrantLock源码探究1:非公平锁的获取和释放
1.AQS简单介绍 Sync是ReentrantLock的一个内部类,它继承了AbstractQueuedSynchronizer,即AQS,在CountDownLatch.FutureTask. ...
- 深入了解ReentrantLock中的公平锁和非公平锁的加锁机制
ReentrantLock和synchronized一样都是实现线程同步,但是像比synchronized它更加灵活.强大.增加了轮询.超时.中断等高级功能,可以更加精细化的控制线程同步,它是基于AQ ...
- 深入分析ReentrantLock公平锁和非公平锁的区别
在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...
- Java之ReentrantLock公平锁和非公平锁
在Java的ReentrantLock构造函数中提供了两种锁:创建公平锁和非公平锁(默认).代码如下: public ReentrantLock() { sync = new NonfairSync( ...
- 深入分析ReentrantLock公平锁和非公平锁的区别 (转)
在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...
- ReentrantLock中的公平锁与非公平锁
简介 ReentrantLock是一种可重入锁,可以等同于synchronized的使用,但是比synchronized更加的强大.灵活. 一个可重入的排他锁,它具有与使用 synchronized ...
- 聊聊ReentrantLock基于AQS的公平锁和非公平锁的实现区别
ReentrantLock锁的实现是基于AQS实现的,所以先简单说下AQS: AQS是AbstractQueuedSynchronizer缩写,顾名思义:抽象的队列同步器,它是JUC里面许多同步工具类 ...
随机推荐
- 4.4.3 Java中的指针:Unsafe类
java多线程编程的无锁CAS底层都是通过 Unsafe进行操作的:源码如下 public final boolean compareAndSet(int expect, int update) { ...
- NArrange 配置与使用
1. 在VS中设置一下就可以用了 2.It is also recommended to setup a restore command using the same parameters, repl ...
- [GO]多任务的资源竞争问题
package main import ( "fmt" "time" ) func Printer(s string) { for _, data := ran ...
- HDU 2602 Bone Collector (01背包DP)
题意:给定一个体积,和一些物品的价值和体积,问你最大的价值. 析:最基础的01背包,dp[i] 表示体积 i 时最大价值. 代码如下: #pragma comment(linker, "/S ...
- 说说JVM中的操作码
JVM操作码 加载与存储操作码 load --从局部变量加载值到栈上 ldc --从池中加载常量到栈上 store --把值从栈中移走,存到局部变量中 dup --复制栈顶的值 getField -- ...
- Tango Java API常数
Tango Java API Constants常数 Constant Field Values常数字段值 Contents com.google.* com.google.* com.google. ...
- T4模板调用反射
<#@ template debug="false" hostspecific="true" language="C#" #> ...
- 响应者链条,iOS中touchs事件的处理流程。
用户在使用app的时候,会产生各样的事件.在iOS中的事件可以分为三种 触摸事件(Touch Event) 加速计事件(Accelerometer Event) 远程控制事件(Remote Contr ...
- Redis 工具类
项目里的Redis 工具类,写下来以备后用 public class RedisConnector { public class RedisParseResult<T> { public ...
- Linux下卸载删除.Net Core
最近在技术博客和技术交流群遇到很多小伙伴们在Linux下更新或者安装.Net Core SDK后dotnet命令无法识别等问题,现如下解决: 卸载SDK命令 sudo yum remove dotne ...