`

AQS、ReentrantLock、CLH锁 、MCS锁 分析

阅读更多

1. ReentrantLock的介绍

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重入性(关于synchronized可以看这篇文章),synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁。

 

2. 重入性的实现原理

要想支持重入性,就要解决两个问题:1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。通过这篇文章,我们知道,同步组件主要是通过重写AQS的几个protected方法来表达自己的同步语义。针对第一个问题,我们来看看ReentrantLock是怎样实现的,以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire:

 

final boolean nonfairTryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取

    if (c == 0) {

        if (compareAndSetState(0, acquires)) {

            setExclusiveOwnerThread(current);

            return true;

        }

    }

    //2.若被占有,检查占有线程是否是当前线程

    else if (current == getExclusiveOwnerThread()) {

        // 3. 再次获取,计数加一

        int nextc = c + acquires;

        if (nextc < 0) // overflow

            throw new Error("Maximum lock count exceeded");

        setState(nextc);

        return true;

    }

    return false;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

这段代码的逻辑也很简单,具体请看注释。为了支持重入性,在第二步增加了处理逻辑,如果该锁已经被线程所占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加1返回true,表示可以再次获取成功。每次重新获取都会对同步状态进行加一的操作,那么释放的时候处理思路是怎样的了?(依然还是以非公平锁为例)核心方法为tryRelease:

 

protected final boolean tryRelease(int releases) {

    //1. 同步状态减1

    int c = getState() - releases;

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

        throw new IllegalMonitorStateException();

    boolean free = false;

    if (c == 0) {

        //2. 只有当同步状态为0时,锁成功被释放,返回true

        free = true;

        setExclusiveOwnerThread(null);

    }

    // 3. 锁未被完全释放,返回false

    setState(c);

    return free;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

代码的逻辑请看注释,需要注意的是,重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。到现在我们可以理清ReentrantLock重入性的实现了,也就是理解了同步语义的第一条。

 

3. 公平锁与公平锁

ReentrantLock支持两种锁:公平锁和非公平锁。何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。ReentrantLock的构造方法无参时是构造非公平锁,源码为:

 

public ReentrantLock() {

    sync = new NonfairSync();

}

1

2

3

另外还提供了另外一种方式,可传入一个boolean值,true时为公平锁,false时为非公平锁,源码为:

 

public ReentrantLock(boolean fair) {

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

}

1

2

3

在上面非公平锁获取时(nonfairTryAcquire方法)只是简单的获取了一下当前状态做了一些逻辑处理,并没有考虑到当前同步队列中线程等待的情况。我们来看看公平锁的处理逻辑是怎样的,核心方法为:

 

protected final boolean tryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    if (c == 0) {

        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;

  }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

这段代码的逻辑与nonfairTryAcquire基本上一直,唯一的不同在于增加了hasQueuedPredecessors的逻辑判断,方法名就可知道该方法用来判断当前节点在同步队列中是否有前驱节点的判断,如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点的话,再才有做后面的逻辑判断的必要性。公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。

 

公平锁 VS 非公平锁

 

公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

 

公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

 

 

2 AQS

    AbstractQueuedSynchronizer简称AQS,是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的大量细节问题。

    AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。如图

     AQS中还有一个表示状态的字段state,例如ReentrantLocky用它表示线程重入锁的次数,Semaphore用它表示剩余的许可数量,FutureTask用它表示任务的状态。对state变量值的更新都采用CAS操作保证更新操作的原子性。

    AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,这个类只有一个变量:exclusiveOwnerThread,表示当前占用该锁的线程,并且提供了相应的get,set方法。

    理解AQS可以帮助我们更好的理解JCU包中的同步容器。

3 lock()与unlock()实现原理

3.1 基础知识

    ReentrantLock是Lock的默认实现之一。那么lock()和unlock()是怎么实现的呢?首先我们要弄清楚几个概念

  • 可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。
  • 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
  • 公平锁与非公平锁。公平锁是指多个线程同时尝试获取同一把锁时,获取锁的顺序按照线程达到的顺序,而非公平锁则允许线程“插队”。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。
  • CAS操作(CompareAndSwap)。CAS操作简单的说就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。

 

CLH锁

CLH(Craig, Landin, and Hagersten  locks): 是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

 

 

MCS锁

MSC与CLH最大的不同并不是链表是显示还是隐式,而是线程自旋的规则不同:CLH是在前趋结点的locked域上自旋等待,而MCS是在自己的

结点的locked域上自旋等待。正因为如此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。

 

<audio controls="controls" style="display: none;"></audio>

<audio controls="controls" style="display: none;"></audio>

分享到:
评论

相关推荐

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    根据给定文件的信息,我们可以深入理解AQS(AbstractQueuedSynchronizer)独占锁之ReentrantLock的源码分析及其实现原理。这不仅包括ReentrantLock本身的特性,还包括了其背后的AQS框架是如何工作的。 ### 一、管程...

    AQS和ReentrantLock.pdf

    AQS和ReentrantLock.pdf

    【并发编程】简单化理解AQS和ReentrantLock.pdf

    AQS和`ReentrantLock`是Java并发编程中重要的组成部分,通过对它们的理解和掌握,可以更好地设计和实现高性能的并发程序。通过本文的学习,读者可以了解到这些核心概念和技术的实际应用,并能够根据具体的业务需求...

    ReentrantLock源码详解--公平锁、非公平锁

    ReentrantLock源码详解--公平锁、非公平锁 ReentrantLock是一种重入锁,实现了Lock接口,能够对共享资源重复加锁,即当前线程获取该锁再次获取不会被阻塞。ReentrantLock的可重入性是通过继承AQS...

    ReentrantLock源码分析

    ReentrantLock是一个基于`AbstractQueuedSynchronizer`(AQS)实现的高级锁工具类。与传统的synchronized关键字相比,ReentrantLock提供了更多控制手段,比如可以指定是否公平锁、支持中断等特性。 #### 二、...

    JUC AQS的加解锁.pdf

    它提供了实现排他模式(独占式)和共享模式锁的机制,被广泛应用于Java并发包中的ReentrantLock、Semaphore、CountDownLatch等组件中。 首先,AQS是一个抽象类,它的主要特点是使用一个int类型的变量(state)表示...

    浅谈Java并发 J.U.C之AQS:CLH同步队列

    浅谈Java并发 J.U.C之AQS:CLH同步队列 在 Java 并发编程中,J.U.C(Java Utility Classes)提供了一些高效的并发工具,其中AQS(AbstractQueuedSynchronizer)是一个核心组件之一。AQS内部维护着一个FIFO队列,即...

    ReentrantLock流程浅析

    总结,ReentrantLock通过AQS和CLH队列实现了一种灵活且高效的锁机制,既支持公平锁又支持非公平锁,同时具备可重入、中断和超时等待的能力。理解和掌握ReentrantLock的工作原理对于编写高并发、高性能的Java程序至关...

    AQS源码分析 (1).pdf

    接下来,我们来具体分析一下AQS的源码。AQS中定义了一个名为state的volatile变量,用于表示同步状态。这个变量有三种操作方法:getstate()、setstate()和compareAndSetState(),分别用于获取、设置和原子性地更新...

    java锁详解.pdf

    1. 锁的原理:ReentrantLock 锁是基于 AQS(AbstractQueuedSynchronizer)机制来实现的。 2. 锁的分类:ReentrantLock 锁可以分为公平锁和非公平锁两种。 3. 公平锁:公平锁是 ReentrantLock 锁的一种实现方式,确保...

    7 AQS源码分析.docx

    本文将详细分析AQS的源码,探讨其工作机制,以及在Java中如何实现不同类型的锁。 首先,我们需要了解锁的基本类型。在Java中,锁主要分为两类:悲观锁和乐观锁。悲观锁认为并发操作会导致数据不一致,因此在操作...

    Java 多线程与并发(11-26)-JUC锁- ReentrantLock详解.pdf

    ReentrantLock的内部类Sync继承自AQS,进一步分为FairSync(公平锁)和NonfairSync(非公平锁)两个子类。公平锁确保线程按照它们请求锁的顺序获取锁,而非公平锁则不保证这种顺序,可能会有线程插队获取锁。 **...

    aqs_demo.rar

    在具体实践中,AQS常用于实现独占锁和共享锁,如ReentrantLock。ReentrantLock提供了公平锁和非公平锁两种模式,公平锁保证了等待最久的线程优先获得锁,而非公平锁则更注重性能,可能让等待时间短的线程优先获取。...

    ReentrantLock源码的使用问题详解.docx

    总的来说,ReentrantLock通过AQS提供的机制,结合公平和非公平的策略,实现了可重入锁的功能,从而在多线程环境下提供了灵活的锁控制。理解ReentrantLock的源码有助于我们更好地掌握并发编程中的锁机制,以优化并发...

    第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()1

    总结,ReentrantLock通过内部类和AQS实现了高度定制化的锁机制,非公平锁和公平锁的实现方式不同,分别适用于不同的并发场景。理解其工作原理有助于我们在实际开发中更有效地利用并发资源,提高程序性能。

    ReentrantLock 实现原理 1

    ReentrantLock 的实现原理基于 AQS(AbstractQueuedSynchronizer),是一个重入锁,允许一个线程反复地获取锁而不会出现自己阻塞自己的情况。 ReentrantLock 的构造方法可以指定锁的类型,包括公平锁和非公平锁。...

    ReentrantLock解析

    总的来说,ReentrantLock通过AQS和Unsafe实现了高效且灵活的锁机制,不仅支持可重入,还有公平和非公平策略的选择。理解其工作原理有助于我们编写更高效、更安全的多线程代码。在实际开发中,可以根据需求选择使用...

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    重点在于ReentrantLock的分析,它是基于AQS实现的互斥锁。相比synchronized,它提供了更多特性,如手动加锁解锁,以及公平和非公平锁的选择。公平锁(FairSync)和非公平锁(NonfairSync)的实现是通过AQS的扩展来...

    JUC核心类AQS的底层原理

    `ReentrantLock`是通过AQS机制实现的一种可重入锁,其具体实现细节如下: 1. **ReentrantLock的创建**:创建`ReentrantLock`实例时,会创建一个`Sync`内部类的实例,该内部类继承自`AbstractQueuedSynchronizer`。`...

Global site tag (gtag.js) - Google Analytics