关于java:JDK成长记18-ReentrantLock-1-通过首次加锁初识AQS

42次阅读

共计 8289 个字符,预计需要花费 21 分钟才能阅读完成。

上一章你应该把握了 Atomic 的底层原理 -CAS。接下来进入另一个重要的一个常识 AQS。咱们通过 ReentrantLock 这个类来讲讲 AQS 这个常识。

从上图能够看出,ReentractLock、ReadWriteReentractLock,这些锁 API 底层是基于 AQS+CAS+volatile 来实现的,个别不会间接应用,常应用的是一些并发汇合 API,然而它们的底层大多还是基于 ReentrantLock 或者 AQS 来实现的。

ReentrantLock 属于 java 并发包里的底层的 API,专门撑持各种 java 并发类的底层的逻辑实现。

ReenranctLock 的内容比拟多,打算分 6 节来讲。

  • 第一节讲一下初识 ReenranctLock 加锁的 AQS 底层原理
  • 第二节讲一下 ReenranctLock 加锁入队的 AQS 底层原理
  • 第三节讲一下 ReenranctLock 开释锁的底层原理
  • 第四节讲一下 ReenranctLock 锁的可重入、偏心、非偏心
  • 第五节讲一下 ReentrantReadWriteLock 读写锁的原理
  • 第六节讲一下 ReenranctLock 中 condition 的利用

Hello ReentrantLock

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>Hello ReentrantLock</span></h3></div>

很多人可能没有用过 ReentrantLock,在一些并发状况下因为要保障一些原子性操作,可能也会用到。然而大多数人很少接触高并发的场景,所以用这个类的人可能很少。

然而在一些开源我的项目中还是有应用到的,比方 Spring Cloud 的 Eureka 组件。有时候面试也常常考 AQS 或者并发汇合的问题。所以把握 ReentrantLock 的原理是十分有必要的。这样你能够轻车熟路的了解它在开源我的项目的应用,更不会在面试的时候被问住。

第一点还是先来看个 HelloWorld 的例子。咱们为了保障某些操作同一时间只能有一个线程操作,会对整个操作加一个锁。除了 synchronized 之外,咱们还能够应用 ReentrantLock。假如有一个操作是 j ++。

那么 Hello ReentrantLock 代码如下:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {

 static int j = 1;

 public static void main(String[] args) {ReentrantLock reentrantLock = new ReentrantLock();
  for(int i=0;i<10;i++){new Thread(()->{reentrantLock.lock();
      try{System.out.println(Thread.currentThread().getName()+"- 后果:"+j++);
      }catch (Exception e){ }finally {reentrantLock.unlock();
      }
    }).start();}
 }
}

从一张图先鸟瞰下 ReentrantLock 外围的 3 个小组件

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 从一张图先鸟瞰下 ReentrantLock 外围的 3 个小组件 </span></h3></div>

ReentrantLock 外围有 3 个组件:state、owner、AQS(形象队列同步器,简略的说就是一个期待队列 Queue)。如下图所示:

这里你能够先有个概念就行,你之后会具体的理解到这几个组件作用的。

从 JDK 源码层面找一下对应的组件

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 从 JDK 源码层面找一下对应的组件 </span></h3></div>

当你有了下面这张图的印象后,咱们通过 Hello ReentrantLock 来剖析下它的组件在源码里的体现。

首先还是简略看下 ReentrantLock 的源码脉络:

它次要脉络有:

1)一些 API 办法

2)3 个外部类,Sync、NonfairSync、Sync

3)1 个成员变量 Sync 对象

理解了源码的大体脉络后,接下来,剖析下它的应用过程,首先必定是创立 ReentrantLock,让咱们来看看构造函数做了些什么。代码如下:

  public ReentrantLock() {sync = new NonfairSync();
  }

发现外部创立了对象,赋值给了 sync 变量。创立了一个外部类 NonfairSync。持续深刻能够发现如下代码关系:

static final class NonfairSync extends Sync {


}

abstract static class Sync extends AbstractQueuedSynchronizer {

原来 sync 变量创立的对象是 NonfairSync。它的父类是 Sync,而这个类的父类是一个 AbstractQueuedSynchronizer。

你能够猜测下,AbstractQueuedSynchronizer 这个是什么货色?从名字上看叫做形象队列同步器,缩写是 AQS。咿?这个就是之前提到的 AQS 啊。

认真看一下这个父类的脉络:

首先也是一对办法,然而变量很有意思,UnSafe 类、head/tail+Node 外部类?这让你想到了什么?

没错,上一节刚接触过的 Aotmic 类 Unsafe 能够用作 CAS 操作的类,head/tail+Node 外部类这不是 LinkedList 的数据结构么?这个就是下面提到过 ReentrantLock 的 3 个小组件之一——期待队列 Queue

等等,还有一个 int state。这个就是下面提到过 ReentrantLock 的 3 个小组件之一——state 变量。你能够看到这几个变量对应的代码如下:

  private transient volatile Node head;

  private transient volatile Node tail;
 
  private volatile int state;

 

state 和 期待队列都看到了,owner 去哪里了?原来 AQS 还有一个父类。叫做 AbstractOwnableSynchronizer。

public abstract class AbstractQueuedSynchronizer

 extends AbstractOwnableSynchronizer

 implements java.io.Serializable {

}



public abstract class AbstractOwnableSynchronizer

 implements java.io.Serializable {protected AbstractOwnableSynchronizer() { }

 private transient Thread exclusiveOwnerThread;

 protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}

 protected final Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}

}

下面这个 exclusiveOwnerThread 不就是独占的 owner 线程的意思么?原来在这里。这就是 3 个小组件中的最初一个组件 -owner线程。

到这里你就能够失去如下所示的源码层面的组件图:

从 JDK 源码层面了解 AQS 的线程第一次加锁

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 从 JDK 源码层面了解 AQS 的线程第一次加锁 </span></h3></div>

当你有了 ReentrantLock 加锁过程的这个概念后,来剖析下源码就很简略多了。

lock 办法源码如下:

  public void lock() {sync.lock();
  }

间接调用了 ReentrantLock 的外部类,Sync 组件的 lock 办法,而 Sync 组件 lock 办法是形象的。如下:

abstract static class Sync extends AbstractQueuedSynchronizer {abstract void lock();
}

sync 这变量,在之前的构造函数中,理论创立的是 AbstractQueuedSynchronizer(AQS)的子类:NonfairSync。所以找到对应的 lock 办法代码如下:

  static final class NonfairSync extends Sync {final void lock() {if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
     else
       acquire(1);
   }

   protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
   }
  }

首先进行的操作就是一个 CAS,更新了 volatile 变量 state,由 0 变为 1。底层应用的是 Unsafe 类操作的。这个和 Aotmic 类的底层 CAS 没什么区别,是相似的。

  protected final boolean compareAndSetState(int expect, int update) {
   // See below for intrinsics setup to support this
   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  }

之后如果没有别的线程并发进行 CAS 操作的话,这个批改 state 的 CAS 操作会胜利,并且返回 true。接着就会执行 setExclusiveOwnerThread 办法了。这个办法代码如下:

  protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}

理论就是设置了以后加锁的线程 owner。接着整个 lock 办法就完结了。

final void lock() {if (compareAndSetState(0, 1))
      setExclusiveOwnerThread(Thread.currentThread());
    else
      acquire(1);
  }

最终你会失去如下所示的流程图:

小结 & 思考

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 小结 & 思考 </span></h3></div>

其实通过这节,你能够发现,其实 ReentrantLock 就是基于 3 个属性来实现的,只不过是通过抽象类封装了公共的属性和操作,而这个抽象类常被咱们成为 AQS。

第一次加锁其实次要就是

1、CAS 操作一个 volatile 的 int state 从 0 ->1

2、是指一个 onwerThread 为加锁线程

3、如果没有竞争的状况,和 Queue 队列没关系的。

大家学完一个技术后,肯定要一会思考和提炼关键点、形象思维,这个是十分重要的!

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0