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

JDK成长记18( ReentrantLock (1) 通过首次加锁初识AQS)
文章图片

上一章你应该掌握了Atomic的底层原理-CAS。接下来进入另一个重要的一个知识AQS。我们通过ReentrantLock这个类来讲讲AQS这个知识。
JDK成长记18( ReentrantLock (1) 通过首次加锁初识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 Hello ReentrantLock

很多人可能没有用过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个小组件 从一张图先鸟瞰下ReentrantLock核心的3个小组件

ReentrantLock核心有3个组件:state、owner、AQS(抽象队列同步器,简单的说就是一个等待队列Queue)。如下图所示:
JDK成长记18( ReentrantLock (1) 通过首次加锁初识AQS)
文章图片

这里你可以先有个概念就行,你之后会详细的了解到这几个组件作用的。
从JDK源码层面找一下对应的组件 从JDK源码层面找一下对应的组件

当你有了上面这张图的印象后,我们通过Hello ReentrantLock来分析下它的组件在源码里的体现。
首先还是简单看下ReentrantLock的源码脉络:
JDK成长记18( ReentrantLock (1) 通过首次加锁初识AQS)
文章图片

它主要脉络有:
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啊。
仔细看一下这个父类的脉络:
JDK成长记18( ReentrantLock (1) 通过首次加锁初识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成长记18( ReentrantLock (1) 通过首次加锁初识AQS)
文章图片

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

当你有了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); }

最终你会得到如下所示的流程图:
JDK成长记18( ReentrantLock (1) 通过首次加锁初识AQS)
文章图片

小结&思考 小结&思考

其实通过这节,你可以发现,其实ReentrantLock就是基于3个属性来实现的,只不过是通过抽象类封装了公共的属性和操作,而这个抽象类常被我们成为AQS。
第一次加锁其实主要就是
1、CAS操作一个volatile的int state从0->1
2、是指一个onwerThread为加锁线程
3、如果没有竞争的情况,和Queue队列没关系的。
【JDK成长记18( ReentrantLock (1) 通过首次加锁初识AQS)】大家学完一个技术后,一定要一会思考和提炼关键点、抽象思想,这个是非常重要的!
本文由博客一文多发平台 OpenWrite 发布!

    推荐阅读