JAVA多线程详解(三):ReentrantLock实现原理与源码分析

本文详细介绍了JAVA多线程中ReentrantLock的工作原理,包括其内部使用的CAS操作、CLH队列锁的概念以及ReentrantLock的整体架构。通过源码分析,揭示了非公平锁和公平锁的加锁、释放锁流程,展示了ReentrantLock如何确保线程安全性。
摘要由CSDN通过智能技术生成

刚学java的多线程通信时,往往只能知道在多线程访问共享资源时加上锁,保证多线程访问共享资源时的同步操作。利用ReentrantLock的lock()方法加锁,ReentrantLock的unlock()方法释放锁。那么加锁和释放锁的内部是怎么实现的呢?其源码解释的比较清晰。下面将从源码的整体架构和具体细节来分析ReentraLock的实现原理。由于大量使用了CAS操作,先介绍CAS的实现原理。

一、CAS原理

是compare and swap的缩写,也就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 也就是在修改V位置的值之前,先将该位置的值与“认为”当前位置原先的值进行比较,若相等,则将内存V的值修改为新值B,否则认为值被修改了,则当前操作不修改V的值。(cas操作往往返回操作是否成功,也有返回当前V中的值)。
CAS仅仅只是一种修改值的手段,单独的CAS并不能保证多线程操作的安全性,其往往要和volatile变量和for循环合作才能保证多线程操作的正确性。volatile保证线程的可见性,for循环则会在CAS操作成功的情况下退出,在失败的情况下更新预期原值(A)为最新的V中存的值然后再次尝试修改直到修改成功,常见的操作如下所示:

//以下代码为AQS中的一段源代码,采用的CAS操作
//使用volatile变量保证线程可见性
private transient volatile Node tail;
private Node enq(final Node node) {
   
        for (;;) {
      //循环进行多次操作
            Node t = tail;   //获得最新的尾结点值
            if (t == null) {
    // Must initialize   //若尾结点为空,则表示队列为空
                if (compareAndSetHead(new Node()))  //CAS操作设置头结点
                    tail = head;   //CAS操作成功,则将尾结点修改
            } else {
   
                node.prev = t;      
                if (compareAndSetTail(t, node)) {
      //CAS操作设置尾结点成功
                    t.next = node;                      //则执行后续操作
                    return t;
                }
            }
        }
    }

以上就是常见的CAS操作利用循环和volatile变量保证设置的正确性。当前线程设置成功 后,其他线程则无法设置成功,必须第二次循环才有可能设置成功。

二、CLH队列锁介绍

CLH是一种种基于链表的可扩展、高性能、公平的自旋锁,申请锁的线程在本地变量上自旋,不断读取其前驱节点的锁状态,若前驱锁状态为false时则退出自旋开始执行。
CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程须要获取锁,且不释放锁。为false表示线程释放了锁。
在这里插入图片描述
如上图所示,这是一种单向链表,每个节点有一个指向其前驱节点的引用。当一个线程申请锁时,会被包装为一个QNode并使用CAS操作加到队列尾部,然后判断前驱的状态开始自旋。

while (pred.locked) //若前驱为true则自旋,若为false则退出自旋';
{
     
}

则利用此队列实现公平的自旋锁,先来先执行,后来后执行。
AQS在CLH队列的基础上进行了扩展。

三、ReentrantLock整体框架

先来看看ReentrantLock都用到了哪些设计模式
1、策略模式
以下为ReentrantLock的部分代码,首先其持有Sync引用,根据不同情况,分别创建了NonfairSync和FairSync的具体执行对象。然后调用的lock()和unlock()的实际执行者是Sync引用的实际对象。这种方式是典型的策略模式。

class ReentrantLock{
   
        private final Sync sync;
        public ReentrantLock() {
   
        sync = new NonfairSync();
    }
        public ReentrantLock(boolean fair) {
   
        sync = fair ? new FairSync() : new NonfairSync();
    }
       public void lock() {
   
        sync.lock();
    }
      public void unlock() {
   
        sync.release(1);
    }
}

2、模板方法模式

abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync

以上为ReentrantLock中的几个静态内部类,锁原理的核心实现在AbstractQueuedSynchronizer抽象类中,同时此类中有5个模板方法,具体的实现类可以选择实现这些方法,来达到特定的锁机制。以一个抽象方法为例看源码

protected boolean tryAcquire(int arg) {
   
        throw new UnsupportedOperationException();
    }

这些模板方法并非抽象方法,所以也说明了具体的实现类中可以选择实现的原理。
下图为类的结构图。
在这里插入图片描述

源码分析

ReentrantLock的加锁的入口函数为lock(),释放锁的入口函数为unlock(),那么我将由lock()为入口来一步一步看公平锁FairSync和非公平锁NonfairSync加锁的实现原理。

1、非公平锁NonfairSync加锁流程

整个加锁的流程如下图所示
在这里插入图片描述
默认情况下ReentrantLock创建的是非公平锁,则在调用lock时调用的是NonfairSync的lock方法,方法首先尝试将state由0变为1,若成功,则表明当前线程抢到锁并设置拥有锁的线程为当前线程。若失败则执行acquire(1)。此处也是非公平锁可以插队的原理。当来了一个线程,不论等待队列里面有没有线程,则都具备申请锁的资格。

public ReentrantLock() 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值