zoukankan      html  css  js  c++  java
  • 5.JUC之JDK自带锁ReentrantLock

    一、初识

    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.公平

     1 // ReentranLock$FiarSync
     2 final void lock() {
     3     acquire(1);
     4 }
     5 public final void acquire(int arg) {
     6  if (!tryAcquire(arg) &&
     7         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     8         selfInterrupt();
     9 }
    10 protected final boolean tryAcquire(int acquires) {
    11     final Thread current = Thread.currentThread();
    12     int c = getState();
    13     if (c == 0) {
    14         if (!hasQueuedPredecessors() &&
    15             compareAndSetState(0, acquires)) {
    16             setExclusiveOwnerThread(current);
    17             return true;
    18         }
    19     }
    20     else if (current == getExclusiveOwnerThread()) {
    21         int nextc = c + acquires;
    22         if (nextc < 0)
    23             throw new Error("Maximum lock count exceeded");
    24         setState(nextc);
    25         return true;
    26     }
    27     return false;
    28 }

      应该从一小段代码里看出三个东西:

      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.不公平

     1 // ReentrantLock#NonFairSync
     2 final void lock() {
     3     if (compareAndSetState(0, 1))
     4         setExclusiveOwnerThread(Thread.currentThread());
     5     else
     6         acquire(1);
     7 }
     8 
     9 public final void acquire(int arg) {
    10  if (!tryAcquire(arg) &&
    11         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    12         selfInterrupt();
    13 }
    14 
    15 protected final boolean tryAcquire(int acquires) {
    16     return nonfairTryAcquire(acquires);
    17 }
    18 
    19 final boolean nonfairTryAcquire(int acquires) {
    20     final Thread current = Thread.currentThread();
    21     int c = getState();
    22     if (c == 0) {
    23         if (compareAndSetState(0, acquires)) { // #1
    24             setExclusiveOwnerThread(current);
    25             return true;
    26         }
    27     }
    28     else if (current == getExclusiveOwnerThread()) {
    29         int nextc = c + acquires;
    30         if (nextc < 0) // overflow
    31             throw new Error("Maximum lock count exceeded");
    32         setState(nextc);
    33         return true;
    34     }
    35     return false;
    36 }

      咋一看,似乎差别不大

      只不过一来就尝试通过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

  • 相关阅读:
    《AI for Game Developers》第七章 A*路径寻找算法 (二)(skiplow翻译)
    芯片科普学习笔记
    sprboot 配置logback 日志输出
    springboot+mybatis 配置双数据源(mysql,oracle,sqlserver,db2)
    vue 封装axios请求
    *arg参数
    pytest mac安装了pytest,但是输入pytest却提示命令不存在
    构建Java Web开发环境
    在CentOS上编译安装PostgreSQL
    在Ubuntu 14.04上使用Eclipse开发和调试PosgreSQL9.3.4
  • 原文地址:https://www.cnblogs.com/xuzekun/p/7486287.html
Copyright © 2011-2022 走看看