深入剖析Java ReentrantLock的源码

目录
  • 1. ReentrantLock的使用
  • 2. ReentrantLock类结构
  • 3. ReentrantLock源码解析
    • 3.1 ReentrantLock构造方法
    • 3.2 非公平锁源码
    • 3.3 公平锁源码
  • 4. 总结

ReentrantLock和Synchronized都是Java开发中最常用的锁,与Synchronized这种JVM内置锁不同的是,ReentrantLock提供了更丰富的语义。可以创建公平锁或非公平锁、响应中断、超时等待、按条件唤醒等。在某些场景下,使用ReentrantLock更适合,功能更强大。

前两篇文章,我们分析了AQS的加锁流程、以及源码实现。当时我们就说了,AQS使用了模板设计模式,父类中定义加锁流程,子类去实现具体的加锁逻辑。所以大部分加锁代码已经在父类AQS中实现了,导致ReentrantLock的源码非常简单,一块学习一下。

先看一下ReentrantLock怎么使用?

1. ReentrantLock的使用

/**
 * @author 一灯架构
 * @apiNote ReentrantLock示例
 **/
public class ReentrantLockDemo {

    public static void main(String[] args) {
        // 1. 创建ReentrantLock对象
        ReentrantLock lock = new ReentrantLock();
        // 2. 加锁
        lock.lock();
        try {
            // 3. 这里执行具体的业务逻辑
        } finally {
            // 4. 释放锁
            lock.unlock();
        }
    }
}

可以看到ReentrantLock的使用非常简单,调用lock加锁,unlock释放锁,需要配置try/finally使用,保证在代码执行出错的时候也能释放锁。

ReentrantLock也可以配合Condition条件使用,具体可以翻一下前几篇文章中BlockingQueue的源码解析,那里面有ReentrantLock的实际使用。

再看一下ReentrantLock的类结构

2. ReentrantLock类结构

// 实现Lock接口
public class ReentrantLock implements Lock {

    // 只有一个Sync同步变量
    private final Sync sync;

    // Sync继承自AQS,主要逻辑都在这里面
    abstract static class Sync extends AbstractQueuedSynchronizer {
    }

    // Sync的两个子类,分别实现了公平锁和非公平锁
    static final class FairSync extends Sync {
    }
    static final class NonfairSync extends Sync {
    }

}

可以看出ReentrantLock的类结构非常简单,实现了Lock接口。

类里面有两个静态内部类,分别实现公平锁和非公平锁。

看一下Lock接口中,定义了哪些方法?

public interface Lock {

    // 加锁
    void lock();

    // 加可中断的锁
    void lockInterruptibly() throws InterruptedException;

    // 尝试加锁
    boolean tryLock();

    // 一段时间内,尝试加锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁
    void unlock();

    // 新建条件状态
    Condition newCondition();
}

就是一些使用锁的常用方法。

在上篇文章中浏览AQS源码的时候,了解到AQS定义了一些有关具体加锁、释放锁的抽象方法,留给子类去实现,再看一下有哪些抽象方法:

// 加独占锁
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 释放独占锁
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 加共享锁
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 释放共享锁
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

// 判断是否是当前线程正在持有锁
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

由于ReentrantLock使用的是独占锁,所以只需要实现独占锁相关的方法就可以了。

3. ReentrantLock源码解析

3.1 ReentrantLock构造方法

// 默认的构造方法,使用非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

// 传true,可以指定使用公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

在创建ReentrantLock对象的时候,可以指定使用公平锁还是非公平锁,默认使用非公平锁,显然非公平锁的性能更好。

先思考一个面试常考问题,公平锁和非公平锁是怎么实现的?

3.2 非公平锁源码

先看一下加锁源码:

从父类ReentrantLock的加锁方法入口:

public class ReentrantLock implements Lock {
    // 加锁入口方法
    public void lock() {
        // 调用Sync中加锁方法
        sync.lock();
    }
}

在子类NonfairSync的加锁方法:

// 非公平锁
static final class NonfairSync extends Sync {

    // 加锁
    final void lock() {
        // 1. 先尝试加锁(使用CAS设置state=1)
        if (compareAndSetState(0, 1))
            // 2. 加锁成功,就把当前线程设置为持有锁线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 3. 没加锁成功,再调用父类AQS中实际的加锁逻辑
            acquire(1);
    }
}

加锁逻辑也很简单,先尝试使用CAS加锁(也就是把state从0设置成1),加锁成功,就把当前线程设置为持有锁线程。

设计者很聪明,在锁竞争不激烈的情况下,很大概率可以加锁成功,也就不用走else中复杂的加锁逻辑了。

如果没有加锁成功,还是需要走else中调用父类AQS的acquire方法,而acquire又需要调用子类的tryAcquire方法。

调用链路就是下面这样:

根据调用链路,实际的加锁逻辑在Sync.nonfairTryAcquire方法里面。

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 非公平锁的最终加锁方法
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 1. 获取同步状态
        int c = getState();
        // 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                // 3. 加锁成功,就把当前线程设置为持有锁线程
                setExclusiveOwnerThread(current);
                return true;
            }
            // 4. 如果当前线程已经持有锁,执行可重入的逻辑
        } else if (current == getExclusiveOwnerThread()) {
            // 5. 加锁次数+acquires
            int nextc = c + acquires;
            // 6. 超过tnt类型最大值,溢出了
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

再看一下释放锁的调用流程,公平锁和非公平锁流程是一样的,最终都是执行Sync.tryRelease方法:

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 释放锁
    protected final boolean tryRelease(int releases) {
        // 1. 同步状态减去释放锁次数
        int c = getState() - releases;
        // 2. 校验当前线程不持有锁,就报错
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 3. 判断同步状态是否等于0,无锁后,就删除持有锁的线程
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
}

再看一下公平锁的源码

3.3 公平锁源码

先看一下公平锁的加锁流程:

最终的加锁方法是FairSync.tryAcquire,看一下具体逻辑:

static final class FairSync extends Sync {

    // 实现父类的加锁逻辑
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 1. 获取同步状态
        int c = getState();
        // 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
        if (c == 0) {
            // 3. 判断当前线程是不是头节点的下一个节点(讲究先来后到)
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
            // 4. 如果当前线程已经持有锁,执行可重入的逻辑
        } else if (current == getExclusiveOwnerThread()) {
            // 5. 加锁次数+acquires
            int nextc = c + acquires;
            // 6. 超过tnt类型最大值,溢出了
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    // 判断当前线程是不是头节点的下一个节点(讲究先来后到)
    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
    }
}

公平锁的释放锁逻辑跟非公平锁一样,上面已经讲过。

4. 总结

看完了ReentrantLock的所有源码,是不是觉得ReentrantLock很简单。

由于加锁流程的编排工作已经在父类AQS中实现,子类只需要实现具体的加锁逻辑即可。

加锁逻辑也很简单,也就是修改同步状态state的值和持有锁的线程exclusiveOwnerThread。

到此这篇关于深入剖析Java ReentrantLock的源码的文章就介绍到这了,更多相关Java ReentrantLock内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 图解Java ReentrantLock的条件变量Condition机制

    目录 概述 ReentrantLock条件变量使用 图解实现原理 await过程 signal过程 概述 想必大家都使用过wait()和notify()这两个方法把,这两个方法主要用于多线程间的协同处理,即控制线程之间的等待.通知.切换及唤醒.而RenentrantLock也支持这样条件变量的能力,而且相对于synchronized 更加强大,能够支持多个条件变量. 最好可以先阅读ReentrantLock系列文章: 图解Java ReentrantLock公平锁和非公平锁的实现 详解Java 

  • 一文带你掌握Java ReentrantLock加解锁原理

    目录 简要总结 ReentrantLock ReentrantLock 结构分析 lock 加锁过程 阻塞前 park方法被唤醒后 unlock 释放锁过程 对比 Synchronized 简要总结 ReentrantLock 实现原理:volatile 变量 + CAS设置值 + AQS + 两个队列 实现阻塞:同步队列 + CAS抢占标记为 valatile 的 state 实现等待唤醒:await :持有锁,park ->加入等待队列 :signal:唤醒下一个等待队列节点,转移进入同步队

  • Java synchornized与ReentrantLock处理并发出现的错误

    目录 什么是并发错误 并发错误是如何产生的 演示并发错误 如何解决并发错误 使用synchornized解决并发错误 使用ReentrantLock解决并发错误 什么是并发错误 多个线程共享操作同一个对象的时候,线程体当中连续的多行操作未必能够连续执行 很可能操作只完成了一部分,时间片突然耗尽,此时,另一个线程抢到时间片,直接拿走并访问了操作不完整的数据(操作不完整的数据,从逻辑上讲是错误数据). 并发错误是如何产生的 根本原因: 多个线程共享操作同一份数据 直接原因: 线程体当中连续的多行语句

  • Java AQS中ReentrantLock条件锁的使用

    目录 一.什么是AQS 1.定义 2.特性 3.属性 4.资源共享方式 5.两种队列 6.队列节点状态 7.实现方法 二.等待队列 1.同步等待队列 2.条件等待队列 三.condition接口 四.ReentrantLock 五.源码解析 一.什么是AQS 1.定义 java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列.条件队列.独占获取.共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的

  • 详解Java ReentrantLock可重入,可打断,锁超时的实现原理

    目录 概述 可重入 可打断 锁超时 概述 前面讲解了ReentrantLock加锁和解锁的原理实现,但是没有阐述它的可重入.可打断以及超时获取锁失败的原理,本文就重点讲解这三种情况.建议大家先看下这篇文章了解下ReentrantLock加锁的基本原理,图解Java ReentrantLock公平锁和非公平锁的实现. 可重入 可重入是指一个线程如果获取了锁,那么它就是锁的主人,那么它可以再次获取这把锁,这种就是理解为重入,简而言之,可以重复获取同一把锁,不会造成阻塞,举个例子如下: @Test p

  • 图解Java ReentrantLock公平锁和非公平锁的实现

    目录 概述 RenentrantLock原理概述 非公平锁实现 演示 加锁原理 释放锁原理 公平锁实现 演示 原理实现 总结 概述 ReentrantLock是Java并发中十分常用的一个类,具备类似synchronized锁的作用.但是相比synchronized, 它具备更强的能力,同时支持公平锁和非公平锁. 公平锁: 指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁. 非公平锁: 多个线程加锁时直接尝试获取锁,能抢到锁到直接占有锁,抢不到才会到等待队

  • java ReentrantLock并发锁使用详解

    目录 一.ReentrantLock是什么 1-1.ReentrantLock和synchronized区别 1-2.ReentrantLock的使用 1-2-1.ReentrantLock同步执行,类似synchronized 1-2-2.可重入锁 1-2-3.锁中断 1-2-4.获得锁超时失败 1-2-5.公平锁 一.ReentrantLock是什么 ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种

  • java ReentrantLock条件锁实现原理示例详解

    目录 引言 条件锁的使用 ReentrantLock.newCondition() Condition.await Condition.signal 引言 在前两篇文章中,我们了解了ReentrantLock内部公平锁和非公平锁的实现原理,可以知道其底层基于AQS,使用双向链表实现,同时在线程间通信方式(2)中我们了解到ReentrantLock也是支持条件锁的,接下来我们来看下,其内部条件锁的实现原理. 条件锁的使用 public static void main(String[] args)

  • java锁机制ReentrantLock源码实例分析

    目录 一:简述 二:ReentrantLock类图 三:流程简图 四:源码分析 lock()源码分析: 非公平实现: 公平锁实现: tryAcquire()方法 公平锁实现: 非公平锁实现: addWaiter() acquireQueued() shouldParkAfterFailedAcquire() parkAndCheckInterrupt() unlock()方法源码分析: tryRelease() unparkSuccessor() 五:总结 一:简述 ReentrantLock是

  • 深入剖析Java ReentrantLock的源码

    目录 1. ReentrantLock的使用 2. ReentrantLock类结构 3. ReentrantLock源码解析 3.1 ReentrantLock构造方法 3.2 非公平锁源码 3.3 公平锁源码 4. 总结 ReentrantLock和Synchronized都是Java开发中最常用的锁,与Synchronized这种JVM内置锁不同的是,ReentrantLock提供了更丰富的语义.可以创建公平锁或非公平锁.响应中断.超时等待.按条件唤醒等.在某些场景下,使用Reentran

  • ReentrantLock从源码解析Java多线程同步学习

    目录 前言 管程 管程模型 MESA模型 主要特点 AQS 共享变量 资源访问方式 主要方法 队列 node节点等待状态 ReentrantLock源码分析 实例化ReentrantLock 加锁 A线程加锁成功 B线程尝试加锁 释放锁 总结 前言 如今多线程编程已成为了现代软件开发中的重要部分,而并发编程中的线程同步问题更是一道难以逾越的坎.在Java语言中,synchronized是最基本的同步机制,但它也存在着许多问题,比如可重入性不足.死锁等等.为了解决这些问题,Java提供了更加高级的

  • Java集合框架源码分析之LinkedHashMap详解

    LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同. LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析). LinkedHashMap同样是非线程安全的,只在单线程环境下使用. LinkedHashMap源码剖析 LinkedHashM

  • Java 读写锁源码分析

    前言 在实际项目中,比如我们有一个共享资源文件,我们程序会会同时并发的去读.写这个共享资源文件,那怎么能保证在高并发场景下安全.高效读写呢?OK,看了下文便知 提示:以下是本篇文章正文内容,案例仅供参考 一.技术介绍 1.ReentranReadWriteLock是什么? ReadWriteLock提供了readLock和writeLock两种锁的操作机制,一个是读锁,一个是写锁,而它的实现类就是ReentranReadWriteLock 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的

  • java中CopyOnWriteArrayList源码解析

    目录 简介 继承体系 源码解析 属性 构造方法 add(Ee)方法 add(intindex,Eelement)方法 addIfAbsent(Ee)方法 get(intindex) remove(intindex)方法 size()方法 提问 总结 简介 CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现,每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组,这样保证了只阻塞写操作,不阻塞读操作,实现读写分离. 继承体系 public

  • Java并发LinkedBlockingQueue源码分析

    目录 简介 常量 构造方法 put await isOnSyncQueue signal 简介 LinkedBlockingQueue是一个阻塞的有界队列,底层是通过一个个的Node节点形成的链表实现的,链表队列中的头节点是一个空的Node节点,在多线程下操作时会使用ReentrantLock锁来保证数据的安全性,并使用ReentrantLock下的Condition对象来阻塞以及唤醒线程. 常量 /** * 链表中的节点类 */ static class Node<E> { //节点中的元素

  • java 中Buffer源码的分析

    java 中Buffer源码的分析 Buffer Buffer的类图如下: 除了Boolean,其他基本数据类型都有对应的Buffer,但是只有ByteBuffer才能和Channel交互.只有ByteBuffer才能产生Direct的buffer,其他数据类型的Buffer只能产生Heap类型的Buffer.ByteBuffer可以产生其他数据类型的视图Buffer,如果ByteBuffer本身是Direct的,则产生的各视图Buffer也是Direct的. Direct和Heap类型Buff

  • Java 中模仿源码自定义ArrayList

    Java 中模仿源码自定义ArrayList 最近看了下ArrayList的源码,抽空根据ArrayList的底层结构写了一个功能简单无泛型的自定义ArrayLsit,帮助自己更好理解ArrayList:,其实现的底层数据结构为数Object组,代码如下: /** * 自己实现一个ArrayList * */ public class MyArrayList { private Object[] elementData; private int size; public int size(){

  • Java从JDK源码角度对Object进行实例分析

    Object是所有类的父类,也就是说java中所有的类都是直接或者间接继承自Object类.比如你随便创建一个classA,虽然没有明说,但默认是extendsObject的. 后面的三个点"..."表示可以接受若干不确定数量的参数.老的写法是Objectargs[]这样,但新版本的java中推荐使用...来表示.例如 publicvoidgetSomething(String...strings)(){} object是java中所有类的父类,也就是说所有的类,不管是自己创建的类还是

  • 通过java.util.TreeMap源码加强红黑树的理解

    在此之前,我们已经为大家整理了很多关于经典问题红黑树的思路和解决办法.本篇文章,是通过分析java.util.TreeMap源码,让大家通过实例来对红黑树这个问题有更加深入的理解. 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红-黑规则:  1) 每个节点不是红色的就是黑色的  2) 根总是黑色的  3) 如果节点是红色的,则它的子节点必须是黑色的(反

随机推荐