JDK成长记18: ReentrantLock (1) 通过首次加锁初识AQS

file

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

file

从上图可以看出,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)。如下图所示:

file

这里你可以先有个概念就行,你之后会详细的了解到这几个组件作用的。

从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的源码脉络:

file

它主要脉络有:

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啊。

仔细看一下这个父类的脉络:

file

首先也是一对方法,但是变量很有意思,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****线程。

到这里你就可以得到如下所示的源码层面的组件图:

file

从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);
  }

最终你会得到如下所示的流程图:

file

小结&思考

<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 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容