ReentrantLock源码分析

  基础知识复习

  synchronized和lock的区别

  synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置成公平锁,也可以配置成非公平锁。通常来说,非公平锁的效率比公平锁要高。

  一个线程使用syn获取锁,除非该线程成功获取到锁,否则将一直阻塞住。而Lock锁提供了lockInterruptibly()接口,提供了可中断的操作

  带超时时间的锁。Lock锁提供了tryLock(long time, TimeUnit unit)带超时时间的获取锁的接口,在等待指定时间后,如果获取不到锁,则放弃获取锁

  自动释放锁。如果用syn加锁,当发生异常时(比方运行时异常),那么jvm会自动释放掉线程持有的锁,而lock锁则不会主动释放,除非调用了unlock接口,因此使用lock锁时有可能导致死锁

  在ReentrantLock上可以绑定多个Condition条件,也就是可以拥有多个等待队列,比如在实现生产者消费者的时候,使用一个队列(锁的队列)存放等待 队列(生产者消费者的队列)有元素的消费者,使用另一个队列(锁的队列)存放等待 队列not full的生产者,相比较synchronized和wait notify而言,避免了错误的唤醒生产者或者消费者的开销

  ReentrantLock 基础分析

  ReentrantLock是lock的一个实现类,独占锁。首先看一下ReentrantLock的内部属性

  private final Sync sync;

  1

  发现在只有一个Sync类型的属性,这个Sync是AQS的一个抽象类如下:

  abstract static class Sync extends AbstractQueuedSynchronizer {

  private static final long serialVersionUID = -5179523762034025860L;

  abstract void lock();

  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;

  }

  protected final boolean tryRelease(int releases) {

  int c = getState() - releases;

  if (Thread.currentThread() != getExclusiveOwnerThread())

  throw new IllegalMonitorStateException();

  boolean free = false;

  if (c == 0) {

  free = true;

  setExclusiveOwnerThread(null);

  }

  setState(c);

  return free;

  }

  ....

  同时在ReentrantLock 内部,实现了Sync2个不同的类,一个是NonfairSync(非公平锁),一个是FairSync(公平锁)。也就是在一开头,synchronized和lock的区别的第一点,Lock可以创建2种不同的锁,根据传入的参数。

  如下源码

  //默认的ReentrantLock无参构造函数,是非公平锁

  public ReentrantLock() {

  sync = new NonfairSync();

  }

  //当传入fasle时,就创建了公平锁。

  public ReentrantLock(boolean fair) {

  sync = fair ? new FairSync() : new NonfairSync();

  }

  AbstractQueuedSynchronizer

  那么Sync的父类是AQS,并发包中的锁底层就是使用了AQS:AbstractQueuedSynchronizer。所以来看一下AQS,那么首先来看下属性:

  //这个是父类AbstractOwnableSynchronizer中的属性,标识了独占模式下获取锁的是哪一个线程

  private transient Thread exclusiveOwnerThread;

  //AQS的数据结构是FIFO的双向队列

  //头部节点

  private transient volatile Node head;

  //尾部节点

  private transient volatile Node tail;

  //同步状态值

  private volatile int state;

  //设置这里使用CAS来更新 主要用于对state的更新

  private static final Unsafe unsafe = Unsafe.getUnsafe();

  private static final long stateOffset;

  private static final long headOffset;

  private static final long tailOffset;

  private static final long waitStatusOffset;

  private static final long nextOffset;

  可以看到有头部节点和尾部节点外,还有一个同步状态值state,该属性在不同的锁中,代表了不同的含义。

  在ReentrantLock中,state可以用来表示当前线程获取锁的可重入次数

  对与ReentrantReadWriteLock来说,高16位代表了读锁,也就是读锁的次数,低16位代表了写锁的可重入次数

  在Semaphore中,state代表了可用信号的个数

  在countdownlatch中,state用来表示计数器当前的值

  所以在ReentrantLock 中 state用来表示当前线程获取锁的可冲入次数。当没有线程持有锁的时候,state为0,当有一个线程获取锁时,state为1。然后当前线程需要再次获取锁时,发现自己已经是锁的持有者,state+1

  看下AQS的Node:

  //是在获取共享资源时放入队列

  static final Node SHARED = new Node();

  //是在获取独占资源时放入对列的

  static final Node EXCLUSIVE = null;

  //线程被取消

  static final int CANCELLED = 1;

  //线程需要被唤醒

  static final int SIGNAL = -1;

  //线程在条件队列里面等待

  static final int CONDITION = -2;

  //释放共享资源时通知其他节点

  static final int PROPAGATE = -3;

  //表示等待的状态,可以为以上几个值

  volatile int waitStatus;

  volatile Node prev;

  volatile Node next;

  //在队列中的线程

  volatile Thread thread;

  Node nextWaiter;

  在已知队列中的节点是这样的结构情况下,来看下何如进行获取资源

  //根据源码中的英文来翻译,就是获取独占资源时调用该方法,

  //也就是使用tryAcquire来改变state的值,如果失败就会把线程封装成Node.EXCLUSIVE放入队列尾部并挂起

  public final void acquire(int arg) {

  if (!tryAcquire(arg) &&

  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

  selfInterrupt();

  }

  //其中 tryAcquire是在实现类中具体实现的

  对应的释放资源

  //tryRelease是在实现类中具体实现的方法,如果成功释放资源,则激活队列中的一个线程,也就是头部节点的线程。

  public final boolean release(int arg) {

  if (tryRelease(arg)) {

  Node h = head;

  if (h != null && h.waitStatus != 0)

  unparkSuccessor(h);

  return true;

  }

  return false;

  }

  以上2个release和acquire,的最常见的实现例子就是Lock中的unlock和lock。但是在AQS源码中还存在着一个类似的方法,acquireInterruptibly ,这个方法和acquire功能一样,但是带Interruptibly 的方法,就是可以对中断进行响应。如果该线程在等待过程中被中断了,那么带Interruptibly 的方法就会抛出异常,也会终端。否则不会。对应了一开始的第二条,lockInterruptibly()可以对中断进行响应。

  再来看一下在AQS中,是怎么进行加入队列操作的。

  //有2种方式,如果尾部节点不为空,就直接加入封装好的node,为空则调用enq()加入队列

  private Node addWaiter(Node mode) {

  Node node = new Node(Thread.currentThread(), mode);

  // Try the fast path of enq; backup to full enq on failure

  Node pred = tail;

  if (pred != null) {

  node.prev = pred;

  if (compareAndSetTail(pred, node)) {

  pred.next = node;

  return node;

  }

  }

  enq(node);

  return node;

  }

  private Node enq(final Node node) {

  for (;;) {

  Node t = tail;

  if (t == null) { // Must initialize

  //加入一个空的node为哨兵节点

  if (compareAndSetHead(new Node()))

  tail = head;

  } else {

  node.prev = t;

  if (compareAndSetTail(t, node)) {

  t.next = node;

  return t;

  }

  }

  }

  }

  也就是说,当队列为空时,在加入第一个节点的时候,会先加入一个哨兵节点。

  ReentrantLock 中的lock

  这里分为2中情况分析lock()方法,之前说过ReentrantLock中的Sync有非公平和公平2中模式。又结合AQS的源码分析,所以我们知道区别在于FairSync 和NonfairSync 2个类中的 tryAcquire 实现不同。

  FairSync 公平情况如下:

  protected final boolean tryAcquire(int acquires) {

  final Thread current = Thread.currentThread();

  int c = getState();

  //当state为0时,就标识空闲,可以被获取

  if (c == 0) {

  //hasQueuedPredecessors是公平策略,

  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的修改都是运行了CAS来更新,同时,在修改state的基础上,使用了公平策略hasQueuedPredecessors是在AQS中的方法

  //判断当前节点是否是队列的第一个节点,如果当前节点有前驱结点返回true,当前队列为空或者当前节点是第一个节点则返回false

  public final boolean hasQueuedPredecessors() {

  Node t = tail;

  Node h = head;

  Node s;

  return h != t &&

  ((s = h.next) == null || s.thread != Thread.currentThread());

  }

  这里最后的判断可以分开来看,h==s则是当前队列为空,直接返回false,如果h!=s且s==null则是,说明有一个元素将要作为AQS的第一个节点入队列,那么返回true,如果,s.thread != Thread.currentThread()) 就代表,第一个元素就不是当前的线程,返回true。

  NonfairSync 非公平情况如下郑州不孕不育医院:http://www.zzfkyy120.com/

  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) {

  //缺少了校验公平的策略,直接对state进行修改

  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的逻辑,lockInterruptibly便是调用了acquireInterruptibly的方法来获取资源

  public final void acquireInterruptibly(int arg)

  throws InterruptedException {

  //判断线程的状态

  if (Thread.interrupted())

  throw new InterruptedException();

  if (!tryAcquire(arg))

  doAcquireInterruptibly(arg);

  }

  ReentrantLock 中的unlock

  ReentrantLock 中的unlock,是没有策略之分,在Snyc中就实现了方法

  protected final boolean tryRelease(int releases) {

  int c = getState() - releases;

  if (Thread.currentThread() != getExclusiveOwnerThread())

  throw new IllegalMonitorStateException();

  boolean free = false;

  if (c == 0) {

  free = true;

  setExclusiveOwnerThread(null);

  }

  setState(c);

  return free;

  }

  unlock的代码就比较好读了,先判断lock的持有线程与当前线程是否一致,然后是的话,就把state和持有线程清空。最后在AQS中,会移除该节点

  当然在AQS中,还存在一个条件队列,在后续文章再谈。

  下面是ReentrantLock 的一个例子,模拟领取优惠卷的情况

  public class ReenTrantLockDemo extends Thread{

  //模拟优惠卷

  private static List array = new ArrayList<>();

  private static Lock lock = new ReentrantLock();

  public Integer get(){

  lock.lock();

  try {

  Integer o= array.get(0);

  array.remove(o);

  return o;

  }catch (Exception e){

  System.out.println("获取出错");

  }finally {

  lock.unlock();

  }

  return -1;

  }

  @Override

  public void run() {

  Integer a = get();

  System.out.println("获取到的优惠卷编号为"+a);

  }

  public static void main(String[] args) {

  for(int i=0;i<10;i++){

  array.add(i);

  }

  ReenTrantLockDemo reenTrantLockDemo = new ReenTrantLockDemo();

  ExecutorService service = Executors.newCachedThreadPool();

  ReenTrantLockDemo demo = new ReenTrantLockDemo();

  for (int i = 0; i < 10; i++) {

  service.submit(demo);

  }

  service.shutdown();

  }

  }

  结果为

  获取到的优惠卷编号为3

  获取到的优惠卷编号为2

  获取到的优惠卷编号为6

  获取到的优惠卷编号为5

  获取到的优惠卷编号为1

  获取到的优惠卷编号为0

  获取到的优惠卷编号为4

  获取到的优惠卷编号为8

  获取到的优惠卷编号为7

  获取到的优惠卷编号为9

  10

  去掉lock的结果为:

  获取到的优惠卷编号为0

  获取到的优惠卷编号为5

  获取到的优惠卷编号为0

  获取到的优惠卷编号为0

  获取到的优惠卷编号为0

  获取到的优惠卷编号为7

  获取到的优惠卷编号为4

  获取到的优惠卷编号为8

  获取到的优惠卷编号为0

  获取到的优惠卷编号为6

  名词解释

  公平锁与非公平锁 :是否按照线程进入阻塞队列的顺序来执行

java多线程---ReentrantLock源码分析的更多相关文章

  1. Java多线程——ReentrantLock源码阅读

    上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...

  2. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  3. Java并发编程之ReentrantLock源码分析

    ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...

  4. 细说并发5:Java 阻塞队列源码分析(下)

    上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  5. JUC AQS ReentrantLock源码分析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...

  6. ReentrantLock 源码分析以及 AQS (一)

    前言 JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题.AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理. A ...

  7. JUC之ReentrantLock源码分析

    ReentrantLock:实现了Lock接口,是一个可重入锁,并且支持线程公平竞争和非公平竞争两种模式,默认情况下是非公平模式.ReentrantLock算是synchronized的补充和替代方案 ...

  8. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  9. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

随机推荐

  1. [工作笔记]JDK版本不同导致的SSL异常

    前言 遇到这个问题得说一下笔者的开发环境,笔者所在公司,平时开发用的web容器是jboss,使用的JDK是oracle的JDK,但是测试和生产环境用的是WAS,JDK用的是IBM的JDK,由于项目的不 ...

  2. 1-1 课程简介 & 2-1 IDEA与Eclipse的不同 & 2-3 Intellij IDEA安装

    ---恢复内容开始--- F:\教程\java-慕课\从网页搭建入门Java Web\Java web\步骤四:常用功能\1.IntelliJ IDEA开发工具入门 1-1 课程简介 2-1 IDEA ...

  3. as3杂记

    一.内存回收方式: 1.引用计数[没有互相引用] 2.标记清除[fp自己检测是否引用,没有引用的清除] 二.通信方式: 1.http:小型页游[charles抓包查看] 2.socket:大型页游[W ...

  4. Ubuntu 下编译libjingle-0.6.14 (转载)

    转自:http://blog.csdn.net/feifei454498130/article/details/8197103 添加依赖库: sudo apt-get install build-es ...

  5. JavaScript 基础 (变量声明, 数据类型, 控制语句)

    创建: 2017/09/16 更新: 2017/09/24 更改标题 [JavaScript 概要]-> [JavaScript 基础] 完成: 2017/09/25 更新: 2017/10/0 ...

  6. now code——小a和黄金街道(欧拉函数和快速幂模板)

    小a和小b来到了一条布满了黄金的街道上.它们想要带几块黄金回去,然而这里的城管担心他们拿走的太多,于是要求小a和小b通过做一个游戏来决定最后得到的黄金的数量.游戏规则是这样的:假设道路长度为米(左端点 ...

  7. android摄像头获取图像——第一弹

    http://www.cnblogs.com/mengyan/archive/2012/09/01/2666636.html 安卓读取视频的几种方式: 详细讲述请参考网址:http://www.cnb ...

  8. Elasticsearch学习记录(入门篇)

    Elasticsearch学习记录(入门篇) 1. Elasticsearch的请求与结果 请求结构 curl -X<VERB> '<PROTOCOL>://<HOST& ...

  9. bzoj1139:[POI2009]Wie

    传送门 状压dp,最短路 spfa似乎特别慢 代码: #include<cstdio> #include<iostream> #include<algorithm> ...

  10. mysql状态查询

    在监控中,都是去探测这些状态数据,然后换算到时间刻度上,像zabbix. show status like 'uptime'; --查看select语句的执行数 show [global] statu ...