通俗易懂的ReentrantLock总结

简介: ReentrantLock 对其操作都转化为对 Sync 对象的操作,由于 Sync 继承了 AQS,所以基本上都可以转化为对 AQS 的操作。如将 ReentrantLock 的 lock 函数转化为对 Sync 的 lock 函数的调用,而具体会根据采用的策略 (如公平策略或者非公平策略) 的不同而调用到 Sync 的不同子类。

👳我亲爱的各位大佬们好
♨️本篇文章记录的为 ReentrantLock 相关内容,适合在学Java的小白,帮助新手快速上手,也适合复习中,面试中的大佬🙉🙉🙉。
♨️如果文章有什么需要改进的地方还请大佬不吝赐教❤️🧡💛
👨‍🔧 个人主页 : 阿千弟

@[toc]

ReentrantLock

ReentrantLock 的底层是通过 AbstractQueuedSynchronizer 实现

类的继承关系

ReentrantLock 实现了 Lock 接口,Lock 接口中定义了 lock 与 unlock 相关操作,并且还存在newCondition 方法,表示生成一个条件。

public class ReentrantLock implements Lock, java.io.Serializable

类的构造函数

  • ReentrantLock () 型构造函数

默认是采用的非公平策略获取锁

public ReentrantLock() {
   
    // 默认非公平策略
    sync = new NonfairSync();
}
  • ReentrantLock (boolean) 型构造函数

可以传递参数确定采用公平策略或者是非公平策略,参数为 true 表示公平策略,否则,采用非公平策略:

public ReentrantLock(boolean fair) {
   
    sync = fair ? new FairSync() : new NonfairSync();
}

16450635837663725.gif

类的内部类 : Sync、NonfairSync、FairSync

ReentrantLock 总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。

在这里插入图片描述

ReentrantLock 有一个 Sync 属性,sync 有俩子类 NonfairSync 和 FairSync

说明: ReentrantLock 类内部总共存在 Sync、NonfairSync、FairSync 三个类,NonfairSync 与FairSync 类继承自 Sync 类,Sync 类继承自 AbstractQueuedSynchronizer抽象类。下面逐个进行分析。

Sync源码分析

abstract static class Sync extends AbstractQueuedSynchronizer {
   
    // 序列号
    private static final long serialVersionUID = xxx;
    // 获取锁
    abstract void lock();
    // 非公平方式获取
    final boolean nonfairTryAcquire(int acquires) {
   
        // 当前线程
        final Thread current = Thread.currentThread();
        // 获取状态
        int c = getState();
        if (c == 0) {
    // 表示没有线程正在竞争该锁
            if (compareAndSetState(0, acquires)) {
    // 比较并设置状态成功,状态0表示锁没有被占用
                // 设置当前线程独占
                setExclusiveOwnerThread(current); 
                return true; // 成功
            }
        }
        else if (current == getExclusiveOwnerThread()) {
    // 当前线程拥有该锁
            int nextc = c + acquires; // 增加重入次数
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 设置状态
            setState(nextc); 
            // 成功
            return true; 
        }
        // 失败
        return false;
    }

    // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
    protected final boolean tryRelease(int releases) {
   
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不为独占线程
            throw new IllegalMonitorStateException(); // 抛出异常
        // 释放标识
        boolean free = false; 
        if (c == 0) {
   
            free = true;
            // 已经释放,清空独占
            setExclusiveOwnerThread(null); 
        }
        // 设置标识
        setState(c); 
        return free; 
    }

    // 判断资源是否被当前线程占有
    protected final boolean isHeldExclusively() {
   
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    // 新生一个条件
    final ConditionObject newCondition() {
   
        return new ConditionObject();
    }

    // Methods relayed from outer class
    // 返回资源的占用线程
    final Thread getOwner() {
           
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    // 返回状态
    final int getHoldCount() {
               
        return isHeldExclusively() ? getState() : 0;
    }

    // 资源是否被占用
    final boolean isLocked() {
           
        return getState() != 0;
    }

    // 自定义反序列化逻辑
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
   
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

在这里插入图片描述

NonfairSync源码分析

NonfairSync 类继承了 Sync 类,表示采用非公平策略获取锁,其实现了 Sync 类中抽象的 lock 方法,源码如下

// 非公平锁
static final class NonfairSync extends Sync {
   
    // 版本号
    private static final long serialVersionUID = xxx;

    // 获得锁
    final void lock() {
   
        if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
            // 把当前线程设置独占了锁
            setExclusiveOwnerThread(Thread.currentThread());
        else // 锁已经被占用,或者set失败
            // 以独占模式获取对象,忽略中断
            acquire(1); 
    }

    protected final boolean tryAcquire(int acquires) {
   
        return nonfairTryAcquire(acquires);
    }
}

说明:从 lock 方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。

FairSyn 源码分析

FairSync 类也继承了 Sync 类,表示采用公平策略获取锁,其实现了 Sync 类中的抽象 lock 方法,源码如下:

// 公平锁
static final class FairSync extends Sync {
   
    // 版本序列化
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
   
        // 以独占模式获取对象,忽略中断
        acquire(1);
    }

    /**
        * Fair version of tryAcquire.  Don't grant access unless
        * recursive call or no waiters or is first.
        */
    // 尝试公平获取锁
    protected final boolean tryAcquire(int acquires) {
   
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取状态
        int c = getState();
        if (c == 0) {
    // 状态为0
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
    // 不存在已经等待更久的线程并且比较并且设置状态成功
                // 设置当前线程独占
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
    // 状态不为0,即资源已经被线程占据
            // 下一个状态
            int nextc = c + acquires;
            if (nextc < 0) // 超过了int的表示范围
                throw new Error("Maximum lock count exceeded");
            // 设置状态
            setState(nextc);
            return true;
        }
        return false;
    }
}

说明:跟踪 lock 方法的源码可知,当资源空闲时,它总是会先判断 sync 队列 (AbstractQueuedSynchronizer中的数据结构) 是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync 类的lock 的方法调用如下,只给出了主要的方法。

说明:可以看出只要资源被其他线程占用,该线程就会添加到 sync queue 中的尾部,而不会先尝试获取资源。这也是和 Nonfair 最大的区别,Nonfair 每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

在这里插入图片描述

核心函数分析

对比公平锁和非公平锁的 tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断 !hasQueuedPredecessors()

hasQueuedPredecessors() 中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

  • 公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;

  • 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(),之后还是需要竞争锁(存在线程竞争的情况下)
    在这里插入图片描述

通过分析 ReentrantLock 的源码,可知对其操作都转化为对 Sync 对象的操作,由于 Sync 继承了 AQS,所以基本上都可以转化为对 AQS 的操作。如将 ReentrantLock 的 lock 函数转化为对 Sync 的 lock 函数的调用,而具体会根据采用的策略 (如公平策略或者非公平策略) 的不同而调用到 Sync 的不同子类。

所以可知,在 ReentrantLock 的背后,是 AQS 对其服务提供了支持

如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对Java后端或者对spring感兴趣的朋友,请多多关注💖💖💖
👨‍🔧 个人主页 : 阿千弟

目录
相关文章
|
安全 Java 开发者
深入解析ReentrantLock重入锁:Java多线程中的利器
深入解析ReentrantLock重入锁:Java多线程中的利器
1590 4
|
7月前
|
Java API
【并发编程】吃透Synchronized
【并发编程】吃透Synchronized
33 1
|
7月前
|
Java
【面试问题】StampedLock 理解与使用
【1月更文挑战第27天】【面试问题】StampedLock 理解与使用
|
存储 安全 Java
synchronized原理详解(通俗易懂超级好)
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
synchronized原理详解(通俗易懂超级好)
|
设计模式 Java API
终于弄懂AQS了
大家好,我是三友~~ 相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题。那你是不是很好奇,这些Lock锁api是如何实现的呢?本文就是来探讨一下这些Lock锁底层的AQS(AbstractQueuedSynchronizer)到底是如何实现的。
|
安全 Java Linux
JUC (java并发编程学习分享篇)
JUC (java并发编程学习分享篇)
77 0
|
Java Spring
通俗易懂的ReentrantLock总结
ReentrantLock 对其操作都转化为对 Sync 对象的操作,由于 Sync 继承了 AQS,所以基本上都可以转化为对 AQS 的操作。如将 ReentrantLock 的 lock 函数转化为对 Sync 的 lock 函数的调用,而具体会根据采用的策略 (如公平策略或者非公平策略) 的不同而调用到 Sync 的不同子类。
58 0
|
Java uml
JUC系列学习(六):ReentrantReadWriteLock的使用及源码解析
`ReentrantReadWriteLock`是一种读写锁,跟`ReentrantLock`一样也是实现了`Lock`,区别在于`ReentrantLock`是独占锁,同一时刻只能有一个线程持有锁,`ReentrantLock`在某些场景下可能会有并发性能的问题。而**ReentrantReadWriteLock是独占锁(写锁)、共享锁(读锁)可以同时存在的一种读写锁,在读操作远大于写操作的场景中,能实现更好的并发性**。当读锁存在时,其他线程仍然可以获取读锁并进行读操作,但是不能获得写锁进行写操作;当写锁存在时,其他线程的读锁、写锁都是不允许的。
|
缓存 API 数据库
通俗易懂读写锁ReentrantReadWriteLock的使用
通俗易懂读写锁ReentrantReadWriteLock的使用
141 0
通俗易懂读写锁ReentrantReadWriteLock的使用
|
设计模式 SpringCloudAlibaba 安全
聊一聊 ReentrantLock 和 AQS 那点事
聊一聊 ReentrantLock 和 AQS 那点事
182 0
聊一聊 ReentrantLock 和 AQS 那点事