学习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。由此实现了非公平锁。

总结

非公平锁和公平锁的两处不同:

  1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。

  2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

作者:小北觅
链接:https://www.jianshu.com/p/2ada27eee90b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

理解ReentrantLock的公平锁和非公平锁的更多相关文章

  1. java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁

    公平锁与非公平锁 ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得 ...

  2. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

  3. ReentrantLock源码探究1:非公平锁的获取和释放

    1.AQS简单介绍 ​ Sync是ReentrantLock的一个内部类,它继承了AbstractQueuedSynchronizer,即AQS,在CountDownLatch.FutureTask. ...

  4. 深入了解ReentrantLock中的公平锁和非公平锁的加锁机制

    ReentrantLock和synchronized一样都是实现线程同步,但是像比synchronized它更加灵活.强大.增加了轮询.超时.中断等高级功能,可以更加精细化的控制线程同步,它是基于AQ ...

  5. 深入分析ReentrantLock公平锁和非公平锁的区别

    在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...

  6. Java之ReentrantLock公平锁和非公平锁

    在Java的ReentrantLock构造函数中提供了两种锁:创建公平锁和非公平锁(默认).代码如下: public ReentrantLock() { sync = new NonfairSync( ...

  7. 深入分析ReentrantLock公平锁和非公平锁的区别 (转)

    在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...

  8. ReentrantLock中的公平锁与非公平锁

    简介 ReentrantLock是一种可重入锁,可以等同于synchronized的使用,但是比synchronized更加的强大.灵活. 一个可重入的排他锁,它具有与使用 synchronized ...

  9. 聊聊ReentrantLock基于AQS的公平锁和非公平锁的实现区别

    ReentrantLock锁的实现是基于AQS实现的,所以先简单说下AQS: AQS是AbstractQueuedSynchronizer缩写,顾名思义:抽象的队列同步器,它是JUC里面许多同步工具类 ...

随机推荐

  1. C++11学习笔记之三lamda表达式,std::function, std::bind

    //lamda //first lamda [] {}; // second lamda []() //or no need () when paramater is null { std::cout ...

  2. 大佬福利之在你眼中 Web 3.0 是什么?(转)

    web 3.0 Web 3.0一词包含多层含义,用来概括互联网发展过程中某一阶段可能出现的各种不同的方向和特征.Web 3.0 充满了争议和分歧,它到底应该什么样?具体的标志点又是什么? Web 2. ...

  3. 编写高质量代码改善C#程序的157个建议——建议94:区别对待override和new

    建议94:区别对待override和new override和new使类型体系应为继承而呈现出多态性.多态要求子类具有与基类同名的方法,override和new的作用就是: 如果子类中的方法前面带有n ...

  4. 我的BootStrap学习笔记

    1.全局样式里面: 1.container:版心 2.col-xx-xx:栅格布局 3.btn btn-default: 按钮,默认按钮样式 4..pull-left  pull-right  cle ...

  5. Inno Setup 通用脚本及简要说明( 一般情况够用了)

    ;以下脚本主要完成创建开始菜单和桌面的快捷方式,目录安装. #define MyAppName "我的软件名" #define MyAppVersion "1.0&quo ...

  6. 微软2014实习生招聘笔试第2题 the k-th string

    Time Limit: 10000msCase Time Limit: 1000msMemory Limit: 256MB Description Consider a string set that ...

  7. [转载].NET开发常用的10条实用代码

    1.读取操作系统和CLR的版本 OperatingSystem os = System.Environment.OSVersion; Console.WriteLine(“Platform: {0}” ...

  8. [SIP01]SIP Header Fields里面各字段用途

    INVITE sip:bob@biloxi.com SIP/2.0 Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds Max-Forw ...

  9. [Perl]Windows 系统 Unicode 文件名操作(新建、重命名、枚举、复制)全攻略

    [Perl] Windows 系统 Unicode 文件名操作(新建.重命名.枚举.复制)全攻略 环境 XP/WIN7 Perl v5.16 编辑整理:PerlMonk.523066680 常见的那些 ...

  10. SDOI2013 淘金

    题目链接:戳我 昨天做的题了,今天补一发题解. 是一个比较奇怪的数位DP.详细的我还是写代码注释里好了,感觉直接说不好描述. 代码如下: #include<iostream> #inclu ...