一文彻底搞懂reentrantlock原理基于aqs的公平锁+非公平锁(代码片段)

噫!微斯人,吾谁与归 噫!微斯人,吾谁与归     2022-12-04     425

关键词:

🍀 JVM已经帮我们内置了synchronized关键字来实现同步,为什么还要引入Lock呢?

首先需要明白synchronized是JVM层面的锁,Lock是API层面的锁,synchonized的灵活度是远不及Lock的;在JDK5时 Lock的效率是优于synchronized,在JDK6开始官方对synchronized进行了大量优化,包括锁升级、锁消除、锁粗化等,事实证明在锁竞争激烈的场景,ReentrantLock还是优于synchronized,但是synchronized还有增长空间,官方也推荐关注synchronized,怎么感觉有种亲儿子的待遇。

认识ReentrantLock的具体实现前,我们应该了解一下ReentrantLock的设计意图是什么?为什么要这样设计?而不是只去看一下具体怎么实现的,然后应付一下面试之类的;我们看别人的代码之前应该搞清楚一个事实,我是奔着提高能力去的,想一想它为什么这样设计?心中多一点思考!看一下Doug Lea大神写的并发代码,我们怎么说也会进步的吧~,或许你会说基础太薄弱,一下子无法理解透;理解一下即可,未来某一刻遇到了类似的设计理念,你会深有同感的。


话不多说,进入今天的正题;今天的核心:ReentrantLock只是指挥的,具体在它的内部类

🍀其中的Lock接口不用多说,这是API层面的锁的统一规范,后续你必须进行遵守,定义一些加锁、解锁、尝试获取锁的接口,因为在API层面,无论你使用什么锁,肯定少不了这几个方法的。
🍀然后ReentranLock内部定义了Sync抽象类,通过组合的方式持有该类

内部的FairSync和NonFairSync通过继承Sync来实现公平锁和非公平锁,扩展性得到提高,耦合度降低;此时你会发现ReentrantLock就像一个"老板"一样,指挥FairSync和NonFairSync这两个主管去做两个任务,而具体的怎么做的一概不管,但是必须给我完成。而FairSync和NonFairSync这两个小主管也会偷懒,把它们相同的部分交给了AbstractQueuedSynchronizer这个底层员,emmm~果然是层层压榨!

首先思考一下ReentrantLock怎么使用的?

官方注释中也定义了一般情况下应该怎么使用ReentrantLock

在try代码块中执行临界区代码,在finally类进行释放锁,保证即便执行临界区代码报错,也会进行锁的释放。

❓ 接下来分析一下ReentrantLock内部有什么东东?(Alt + 7)

📑其中最为突出有3个内部类,首先Sync抽象类,该类继承了AbstractQueuedSynchronizer抽象同步队列(AQS)

AQS可以说是很多同步锁实现的核心,比如说ReentrantLock、Semaphore、CountDownLatch等等,这些同步锁都是基于AQS来实现的(定义一个内部类继承了AQS),所以需要搞清楚AQS这个东西。

AQS即"抽象同步队列",它有5个核心要素:同步状态、等待队列、独占模式、共享模式和条件队列。

(1)同步状态

顾名思义,同步状态就是用来实现锁机制的。如何实现?AQS抽象类中有一个属性state

然后看我们调用lock方法进行加锁的底层实现,首先是调用了ReentrantLock内部类Sync的尝试获取锁方法,看下图

参数传入了一个1,这个如何理解呢?也就是线程CAS尝试将0修改为1,如果修改成功,那么它就是成功抢到锁(这里我说的没包含锁重入的情况),然后我以非公平锁为例,简单分析获取锁源码实现。
需要注意一点就是那个state状态值,为什么会大于1呢?,这时因为ReentrantLock是支持可重入的,一旦该值>1,说明某个线程多次获取了同一把锁(业务复杂时可能会用到)。

(2)等待队列

顾名思义,等待队列就是用来存放等待锁的线程,在AQS抽象类中有一个内部类Node,它是实现双向队列的核心

		//独占锁标识
        static final Node EXCLUSIVE = null;
		//节点处于取消状态,后面会详解
        static final int CANCELLED =  1;
		//标识后续节点需要被唤醒
        static final int SIGNAL    = -1;
		//节点状态	
        volatile int waitStatus;
		//前驱指针
        volatile Node prev;
        //后继指针
        volatile Node next;
        //当前节点绑定的线程
        volatile Thread thread;

AQS同步器将线程封装到了Node里面,维护了一个CHL Node FIFO队列,这是一个非阻塞的FIFO队列,意味着在并发条件下向此队列进行插入和删除时不会发生阻塞。它通过自旋+CAS来保证节点插入和移除的原子性。看下面的AQS中的入队代码

	//节点的入队方法
    private Node addWaiter(Node mode) 
        Node node = new Node(mode);
		//自旋+CAS保证快速插入
        for (;;) 
            Node oldTail = tail;
            //如果存在尾节点,说明同步队列已经被初始化过(也就是该节点不是第一个插入到队列的)
            if (oldTail != null) 
            	//设置将要插入节点的前驱节点指向队列尾节点
            	//注意并发情况下可能有多个节点同时指向尾节点
                node.setPrevRelaxed(oldTail);
                //CAS---设置插入节点的地址为当尾节点的next域
                //设置失败的节点重试(上面的for循环)
                if (compareAndSetTail(oldTail, node)) 
                    oldTail.next = node;//设置tail节点为当前插入节点
                    return node;
                
             else 
            	//队列首次插入节点,则要进行初始化操作
                initializeSyncQueue();
            
        
    

(3)独占模式

顾名思义,用来实现独占锁的,AQS的内部类Node定义的EXCLUSIVE 就是用来标识独占模式的。

(4)共享模式

用来实现共享锁的,AQS的内部类Node定义的SHARED就是用来标识共享模式的。

到这里AQS就介绍的差不多了,该专注于公平锁和非公平锁了。


✨首先公平锁和非公平锁的加锁和解锁都是在AQS中实现的,这两种锁都继承了Sync抽象类,查看该抽象类的具体实现。

abstract static class Sync extends AbstractQueuedSynchronizer 
    
        @ReservedStackAccess
        final boolean nonfairTryAcquire(int acquires) 
			....
        
        @ReservedStackAccess
        protected final boolean tryRelease(int releases) 
       		....
       
		....
    

你会发现它居然在内部了非公锁nonfairTryAcquire获取锁操作,它是因为偏心吗?既然是不公平锁那就不公平一点?😂😂,其实不是的,因为公平锁也会用到这个方法,所以将它抽取到了Sync类中;tryRelease方就不用多说了,用户释放锁,两种锁的释放是一致的。

为什么称之为非公平锁,它的不公平体现在哪里?

竞争锁时会有两方面的势力,被唤醒的CLH队列中的线程和非CLH队列中的线程,它们会同时竞争锁;不会因为你来的早我就把锁让给你,一句话:各凭本事!

具体体现在下面代码中:

锁释放时调用了release方法(上面讲过这个方法),方法内部调用了unparkSuccessor唤醒后继节点方法。

此时如果来了多个线程调用lock方法想要获取锁


最终会调用到nonfairTryAcquire尝试获取锁方法

所以说非公平就体现在这,但是这样的性能是比较好的,因为可能直接省略了唤醒线程这一步骤。

❓获取锁的流程是怎么样的:

为什么称之为公平锁,它的公平体现在哪里?


其实FairSync和NonfairSync的获取锁代码基本上一致,只不过NonfairSync比FairSync多了一步,需要判断当前线程是否是在CLH队列中被唤醒的。

❓ ReentrantLock默认使用的是公平锁还是非公平锁?

是非公平锁,性能优于公平锁,前面解释过。


也可以传入true值,就会使用公平锁。

多线程(十aqs原理-reentrantlock实现)(代码片段)

ReentrantLock介绍ReentrantLock基于AQS实现了公平和非公平的独占锁功能。ReentrantLock定义AQS的同步状态(synchronizationstate)如下:State为0表示锁可用;为1表示被占用;为N表示锁重入的次数,是独占资源。ReentrantLock实现公平锁原理案例... 查看详情

一文彻底搞懂zookeeper(代码片段)

本文是基于CentOS7.9系统环境,进行Zookeeper的学习和使用1.Zookeeper简介1.1什么是ZookeeperZookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。本质上,就是文件系统+通知机制1.2Zookeeper工作机制Zookeepe... 查看详情

一文彻底搞懂zookeeper(代码片段)

本文是基于CentOS7.9系统环境,进行Zookeeper的学习和使用1.Zookeeper简介1.1什么是ZookeeperZookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。本质上,就是文件系统+通知机制1.2Zookeeper工作机制Zookeepe... 查看详情

深度分析springaop,一文带你彻底搞懂springaop底层原理!

SpringAOP我们为什么要使用AOP(面向切面编程)?当我们在现实中完成实际的项目时,我们总是需要在一个“动作”进行前,进行中,或进行后进行一些操作,比如当我们在运行程序时,我们想要进行日志保存,或者在每一个方法... 查看详情

一文彻底搞懂kafka(代码片段)

Kafka的学习和使用本文是基于CentOS7.9系统环境,进行Kafka的学习和使用一、Kafka的简介1.1Kafka基本概念(1)什么是KafkaKafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域(2)消息队列点对点模式... 查看详情

一文彻底搞懂快速幂(原理实现矩阵快速幂)(代码片段)

前言大家好,我是bigsai,之前有个小老弟问到一个剑指offer一道相关快速幂的题,这里梳理一下讲一下快速幂!快速幂是什么?顾名思义,快速幂就是快速算底数的n次幂。你可能疑问,求n次幂算n次叠... 查看详情

一文彻底搞懂快速幂(原理实现矩阵快速幂)(代码片段)

前言大家好,我是bigsai,之前有个小老弟问到一个剑指offer一道相关快速幂的题,这里梳理一下讲一下快速幂!快速幂是什么?顾名思义,快速幂就是快速算底数的n次幂。你可能疑问,求n次幂算n次叠... 查看详情

从reentrantlock的实现看aqs的原理及应用(代码片段)

从ReentrantLock的实现看AQS的原理及应用前言Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队... 查看详情

一文带你彻底搞懂springboot-rabbitmq(代码片段)

一、环境搭建采用maven多module模式,共计创建三个子modulecommon:通用实体信息rabbitmq-publisher:消息发布者,基于SpringBootrabbitmq-subscriber:消息订阅者,基于SpringBoot在消息发布者和订阅者两个项目中加入rabbitm... 查看详情

一文彻底搞懂hbase(代码片段)

本文是基于CentOS7.9系统环境,进行HBase的学习和使用一、HBase的简介1.1HBase基本概念HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库,可以解决HDFS随机写的问题1.2HBase数据模型逻辑上,HBase的数据模型同关系... 查看详情

一文彻底搞懂hbase(代码片段)

本文是基于CentOS7.9系统环境,进行HBase的学习和使用一、HBase的简介1.1HBase基本概念HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库,可以解决HDFS随机写的问题1.2HBase数据模型逻辑上,HBase的数据模型同关系... 查看详情

从reentrantlock的实现看aqs的原理及应用(代码片段)

从ReentrantLock的实现看AQS的原理及应用前言1ReentrantLock1.1ReentrantLock特性概览1.2ReentrantLock与AQS的关联2AQS2.1原理概览2.1.1AQS数据结构2.1.2同步状态State2.2AQS重要方法与ReentrantLock的关联2.3通过ReentrantLock理解AQS2.3.1线程加入等待队列2.3.1.1... 查看详情

图文详解一文全面彻底搞懂hbaseleveldbrocksdb等nosql背后的存储原理:lsm-tree日志结构合并树...(代码片段)

LSM树广泛用于数据存储,例如RocksDB、ApacheAsterixDB、Bigtable、HBase、LevelDB、ApacheAccumulo、SQLite4、Tarantool、WiredTiger、ApacheCassandra、InfluxDB和ScyllaDB等。在这篇文章中,我们将深入探讨LogStructuredMergeTree,又 查看详情

图文详解一文全面彻底搞懂hbaseleveldbrocksdb等nosql背后的存储原理:lsm-tree日志结构合并树...(代码片段)

LSM树广泛用于数据存储,例如RocksDB、ApacheAsterixDB、Bigtable、HBase、LevelDB、ApacheAccumulo、SQLite4、Tarantool、WiredTiger、ApacheCassandra、InfluxDB和ScyllaDB等。在这篇文章中,我们将深入探讨LogStructuredMergeTree,又 查看详情

一文彻底搞懂mysql基础:b树和b+树的区别

一文彻底搞懂MySQL基础:B树和B+树的区别_码农富哥-CSDN博客_b树和b+树有什么区别写在前面大家在面试的时候,肯定都会被问到MySql的知识,以下是面试场景:面试官:对于MySQL,你对他索引原理了解吗... 查看详情

cas和aqs一文搞懂(代码片段)

JAVA多线程,面试官喜欢问的东西一些概念的东西原子性就是指该操作是不可再分的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调... 查看详情

多线程(九aqs原理-简介)

...状态,线程阻塞/唤醒,等待队列管理的操作。平时用的ReentrantLock,CountDownLatch,Semaphore(信号量)都是基于AQS提供API来实现的,他们的不同之处就是对于AQS内部的同步状态(synchronizationstate,int类型)操作不同,来实现的功能不... 查看详情

并发编程面试reentrantlock相关

跟Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?其实,锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。Synchronized通过在对象头中设置标记实现了这一目的,是一种JVM原生的锁实现方式... 查看详情