一文彻底搞懂ReentrantLock原理【基于AQS的公平锁+非公平锁】

🍀 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值,就会使用公平锁。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thecoastlines

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值