一、初识

ReentrantLock出身自jdk1.5,中文名:可重入锁

是Java JDK自带独占锁的唯一实现,是synchronized的升级版

  1.我们之间有个synchronized

  我们已经认识了synchronized了,知道他能把我们实现线程同步提供原子性语义,同时又有可重入性

  同时我们也已经知道可重入性是什么意思,也知道公平性的含义

  可以通过阅读 ReentrantLock 的源码,来加深对sychronized一些特性的理解

  

  2.升级版,体现在哪些方面

  前面说了ReentrantLock是sychronized的升级版,那么ReentrantLock升级了什么,为我们带来了哪些新特性呢?

  1.tryLock 尝试获取锁,直接穿透(无视)公平性

  2.isLocked  当前锁是不是被持有(含自己和他人),用来监控系统(锁)状态

  3.hasQueuedThreads() 提供更多监控

  4.Fair  And  Non-Fair  Model      ReentrantLock提供公平与非公平两个模式

  5.Condition   在Lock内用Condition代替Synchronized 的Object监控

  这些都是非常好用,非常实用的功能,而synchronized却没有的特征,还有还有,ReentrantLock还提供了一个可中断的方法

  

  3.Condition

  当ReentrantLock 当成 synchronized时,你需要把Condition当成Object监控。功能和用法都一样,只是名字不同而已。

  项目    等待    唤醒    唤醒所有    用法

  Object      wait    notify    notifyAll    在synchronized语块内

  Condition   await     singal     singalAll    在Lock语块内

二、ReentrankLock中的公平性

  我们已经认识过公平性了,我们知道她的语义是  是否先到先得。那么它是怎么实现的呢,我们好像还没看过。接下来我们将通过阅读ReentrantLock的源码,来看看他是怎么实现 公平性 的,先上代码:

  1.公平

 // ReentranLock$FiarSync
final void lock() {
acquire(1);
}
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) {
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.公平

  2.可重入

  3.独占式

    JDK在实现锁的时候通常用 ‘0’ 和大于 ‘0’ 表示锁的状态,即没上锁和已上锁

  从这一小段源码里面可以看到

    -ReentrantLock先自己尝试去获取锁,即是检查state的状态码

    1.对于state为0(即锁没被持有的时候),且前面没人在排队,理论上对这种情况处理特别简单,直接上锁就可以了,但实际上并不是,我们最之前在整理AQS框架的时候已经了解了AQS是通过CAS实现同步,即通过一组原子性的操作完成的

    2.当state不为0时,他就会去判断是谁持有,如果是自己的话,依然可以再次获得,这就是可重入性

    同时state自增,此时可以反映两个问题,state还代表递归层级,最多能支持Integer.MAX_Value层

    ReentrantLock在获取锁失败后,就进入等待队列继续等待 

    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();

  2.不公平

 // ReentrantLock#NonFairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
} final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { // #1
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;
}

  咋一看,似乎差别不大

  只不过一来就尝试通过CAS修改条件来获取独占锁。还改了个不要脸的名字叫nonfairTryAcquire

  细心一点,可以看到 #1 语句里面少了条件 !hasQueuedPredecessors(),即是不管前面有没有在排队,就伸手要去取锁。正因为这两个 ‘不道德’的插队操作完成了非公开性,从而获得跟高的OPS。

  ReentrantLock 默认实现就是非公平的,因为它能有更高的吞吐量

三、AQS框架回炉结合ReentrantLock源码分析

  在整理AQS框架的时候,我们说 AQS 框架提供一个基于FIFO等待队列,可以用于构建锁或者其他同步装置的基础框架意在能够成为实现大部分同步需求的基础,他的用法可以参考ReentrantLock的实现

  这里再多说几句,ReentrantLock是实现Lock接口,同时也实现Serializable。Serializable是说它的状态可以被序列,而Lock提供Lock的相关语义和操作。到这里为止,好像跟AQS没什么关系。不过我们说Lock是通过AQS实现同步的。ReentrantLock实际上是通过一个继承自AQS的内部类---同步器Sync,实现同步,从而完成Lock功能的

  再看看ReentrantLock提供两种Sync的实现,从而做到公平和非公平,即FairSync  和  NonFairSync两个类。而Lock的功能完全依赖于Sync,由Sync具体实现。所以说AQS可以用来实现同步锁

  ReentrantLock和AQS的联系(ReentrantLock源码分析):

 abstract static class Sync extends AbstractQueuedSynchronizer 

  ReentrantLock定义了一个Sync内部类,继承自AQS, 有了这个内部类,ReentrantLock才能实现同步,为什么呢,只有有了这个内部类,我们才有了AQS中的FIFO队列,只有有了这个内部类,我们才能重写AQS中的方法

  前面一篇关于AQS的博客提到,继承AQS可以重写五个方法,

  tryAcquire(int arg) 
  tryRelease(int arg) 
  tryAcquireShared(int arg) 
  tryReleaseShared(int arg) 
  isHeldExclusively()

  ReentrantLock重写了tryAcquire 和 tryRelease ,用于实现独占

下面我们来分析一下这些方法在何时被调用:

  首先,当我们在程序中创建一个ReentrantLock时,可以通过构造方法选择是公平的,还是非公平的ReentrantLock,默认是非公平的

static final class NonfairSync extends Sync {
static final class FairSync extends Sync {

  

  a)当我们创建出一个ReentrantLock(默认的非公平),调用其的lock方法,此时,如果是非公平的,实质上会首先将这个锁变为独占锁,原因:当锁未被占用,此时状态码state为0,

通过compareAndSetState(期望值,更新值)返回true(因为期望值和实际值都为0)后,将 state置为1,表明这个锁被我占用了,

  后面再有线程来调用lock方法,此时state为1,执行compareAndSetState会返回false,因为期望值(0)和实际值(1)不一致,只会去调用acquire方法

  b)我们继续跟acquire方法,发现这里一定会调用 tryAcquire,同时也有可能调用acquireQueued

  c)所以我们先跟tryAcquire(1)  

  d)nonfairTryAcquire(1)

  tryAcquire 调的是对应的 nonfairTryAcquire,这是其他线程,并且带过来的参数为1,所以两个条件都不满足,所以这里返回 false

  先判断锁是否被人占了,再判断是不是占的线程是不是自己(这里体现了可重入性)

  e)因为 tryAcquire返回的是 false,所以acquireQueued这个方法会执行,我们跟进acquireQueued

 

  f)这里就用到了我们AQS所提供的那个FIFO等待队列(双链表实现),注意:这里是AQS那个类提供的,而不是ReentrantLock,

  当某个线程获取锁失败后( tryAcquire返回的是 false),进入这个等待队列等待,

  与 synchronize 不一样的是,这里线程不会被阻塞住,而是轮询式地(看到那个For循环没)调用Acquire方法

  

  h)线程在队列中会一直调用这个方法,即当一个线程请求非公平锁时,如果在发出请求的同时该锁变成可用状态(unlock方法被调用,锁的state状态变为0,),那么这个线程会跳过队列中所有的等待线程而获得锁,,当但是这个过程是不公平的,

  在后面我们看到公平锁的时候,会看到加了一个判断条件来进行限定  

注:上面是默认的 ReentrantLock实现(非公平),下面我们来看看ReentrantLock的 公平实现

  这里指出和 非公平实现的几点区别即可:

  a)创建,通过传 true,来得到 公平的ReentrantLock(FarSync)

  b)同样调用 lock方法, 和非公平锁不同,这里不会将这个锁先直接变为 独占锁,而是直接调用 acquire 方法

  c)acquire方法同样一定会调用 tryAcquire,和非公平锁不同,为了保证线程按照他们发出请求的顺序获取锁,会先进行判断(hasQueuedPredecessors),是你这个线程是否在FIFO等待队列中,如果你在队列中,是不允许你有插队的行为的

  注:这个FIFO队列非公平和公平是一样的,是AQS自己维护的,也同样会轮询式地调用Acquire方法

注:前面提到的锁的释放,unlock方法的调用,没有公平性的问题,都是调用 sync 的release方法,

  ReentrantLock没有重写该方法,调用的就是 AQS自己的 release方法

  不要忘了 sync,他是FairSync 和 NonFairSync 父类,同时sync也继承自 AQS,作为ReentrantLock的内部类使用

ReentrantLock源码大概就分析到这,里面的内容当然还有很多,但是核心是这一些,剩余的内容,如Condition等,后面有时间再补充

参考博客:http://blog.csdn.net/zteny/article/details/54999701

5.JUC之JDK自带锁ReentrantLock的更多相关文章

  1. JUC回顾之-可重入的互斥锁ReentrantLock

    1.什么是可重锁ReentrantLock? 就是支持重新进入的锁,表示该锁能够支持一个线程对资源的重复加锁. 2.ReentrantLock分为公平锁和非公平锁:区别是在于获取锁的机制上是否公平. ...

  2. JDK 自带的观察者模式源码分析以及和自定义实现的取舍

    前言 总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,但是可以借鉴其好的思想. java.util.Observer 接口源码分析 该接口十分简单,是各个观察者需要实现的接口 pac ...

  3. JUC原子操作类与乐观锁CAS

    JUC原子操作类与乐观锁CAS ​ 硬件中存在并发操作的原语,从而在硬件层面提升效率.在intel的CPU中,使用cmpxchg指令.在Java发展初期,java语言是不能够利用硬件提供的这些便利来提 ...

  4. 一些JDK自带的性能分析利器

    有时候碰到服务器CPU飙升或者程序卡死之类的问题,一般都不太好定位.这类bug一般都隐藏的比较深并且还可能是偶发性的,比较棘手. 对于此类问题,一般我们都有固定的分析流程.借助于JDK自带的一些分析工 ...

  5. JDK内置锁深入探究

    一.序言 本文讲述仅针对 JVM 层次的内置锁,不涉及分布式锁. 锁有多种分类形式,比如公平锁与非公平锁.可重入锁与非重入锁.独享锁与共享锁.乐观锁与悲观锁.互斥锁与读写锁.自旋锁.分段锁和偏向锁/轻 ...

  6. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  7. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  8. JDK自带的LinkedHashMap来实现LRU算法

    1 代码如下 public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> { private final i ...

  9. [转]JDK自带工具之问题排查场景示例

    最近看到了大量关于java性能调优.故障排查的文章,自己也写了一篇Java调优经验谈.接着此篇文章,其实一直打算写写一些常用调优工具以及它们的惯常用法的.后来在http://java-performa ...

随机推荐

  1. 【html】css、js实现网页内容禁止选中

    网页内容不能选中.复制应该如何实现呢? 通过css *{ moz-user-select: -moz-none; -moz-user-select: none; -o-user-select:none ...

  2. linux生成新的列(awk)

    awk的功能特别强大,其中一个功能就是生成新的列,不论这列是在第一列,最后一列,还是中间,随你任性插入. 举例来说,有下列文件test1.txt test1.txt a b c 1 2 3 现在我想在 ...

  3. Python - Django - ORM Django 终端打印 SQL 语句

    在 settings.py 中添加以下内容: LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'c ...

  4. DBGrid 单击弹出PickList

    type   myGrid = class(TCustomGrid)   end; type   myInplaceEditList = class(TInplaceEditList)   end; ...

  5. 【物联网】esp8266+LCD

    https://blog.csdn.net/qq_40531588/article/details/89515149

  6. 【k8s label】对node添加删除label,并根据label筛选节点

    添加 kubectl label nodes kube-node label_name=label_value kubectl label nodes 1.1.1.1 label_name=label ...

  7. Visual Studio Code + Docker

    前言 VS Code是一个年轻的编辑器,但是确实是非常犀利.通过本篇,老司机带你使用VS Code玩转Docker——相信阅读本篇之后,无论是初学者还是老手,都可以非常方便的玩转Docker了!所谓是 ...

  8. 推荐两本CCF教材

    希望学习电脑程序设计的同学,可以购买如下两本教材,先学习入门篇,再学习基础篇.淘宝.当当.京东均有售.建议选择比较靠谱的网店,避免买到盗版书.

  9. Swoole练习 UDP

    UDP 服务代码 <?php //创建Server对象,监听 127.0.0.1:9502端口,类型为SWOOLE_SOCK_UDP $serv = new swoole_server(&quo ...

  10. 微信小程序之 语言特点

    主页面的CSS样式默认为index.wxss,无需引入