`
Ilovejava1
  • 浏览: 17680 次
  • 性别: Icon_minigender_2
文章分类
社区版块
存档分类
最新评论

Java 重入锁 ReentrantLock 原理分析

阅读更多

本文转载自:http://www.itdaan.com/blog/2018/05/08/b97ffe22c66e8ae19f658fd5715e4007.html

1.简介

可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似。所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生。ReentrantLock 的主要功能和 synchronized 关键字一致,均是用于多线程的同步。但除此之外,ReentrantLock 在功能上比 synchronized 更为丰富。比如 ReentrantLock 在加锁期间,可响应中断,可设置超时等。

ReentrantLock 是我们日常使用很频繁的一种锁,所以在使用之余,我们也应该去了解一下它的内部实现原理。ReentrantLock 内部是基于 AbstractQueuedSynchronizer(以下简称AQS)实现的。所以要想理解 ReentrantLock,应先去 AQS 相关原理。我在之前的文章 AbstractQueuedSynchronizer 原理分析 - 独占/共享模式 中,已经详细分析过 AQS 原理,有兴趣的朋友可以去看看。本文仅会在需要的时候对 AQS 相关原理进行简要说明,更详细的说明请参考我的其他文章。

2.原理

本章将会简单介绍重入锁 ReentrantLock 中的一些概念和相关原理,包括可重入、公平和非公平锁等原理。在介绍这些原理前,首先我会介绍 ReentrantLock 与 synchronized 关键字的相同和不同之处。在此之后才回去介绍重入、公平和非公平等原理。

2.1 与 synchronized 的异同

ReentrantLock 和 synchronized 都是用于线程的同步控制,但它们在功能上来说差别还是很大的。对比下来 ReentrantLock 功能明显要丰富的多。下面简单列举一下两者之间的差异,如下:

特性 synchronized ReentrantLock 相同
可重入
响应中断
超时等待
公平锁
非公平锁
是否可尝试加锁
是否是Java内置特性
自动获取/释放锁
对异常的处理 自动释放锁 需手动释放锁

除此之外,ReentrantLock 提供了丰富的接口用于获取锁的状态,比如可以通过isLocked()查询 ReentrantLock 对象是否处于锁定状态, 也可以通过getHoldCount()获取 ReentrantLock 的加锁次数,也就是重入次数等。而 synchronized 仅支持通过Thread.holdsLock查询当前线程是否持有锁。另外,synchronized 使用的是对象或类进行加锁,而 ReentrantLock 内部是通过 AQS 中的同步队列进行加锁,这一点和 synchronized 也是不一样的。

这里列举了不少两者的相同和不同之处,暂时这能想到这些。如果还有其他的区别,欢迎补充。

2.2 可重入

可重入这个概念并不难理解,本节通过一个例子简单说明一下。

现在有方法 m1 和 m2,两个方法均使用了同一把锁对方法进行同步控制,同时方法 m1 会调用 m2。线程 t 进入方法 m1 成功获得了锁,此时线程 t 要在没有释放锁的情况下,调用 m2 方法。由于 m1 和 m2 使用的是同一把可重入锁,所以线程 t 可以进入方法 m2,并再次获得锁,而不会被阻塞住。示例代码大致如下:

void m1() {
    lock.lock();
    try {
        // 调用 m2,因为可重入,所以并不会被阻塞
        m2();
    } finally {
        lock.unlock()
    }
}

void m2() {
    lock.lock();
    try {
        // do something
    } finally {
        lock.unlock()
    }
}

假如 lock 是不可重入锁,那么上面的示例代码必然会引起死锁情况的发生。这里请大家思考一个问题,ReentrantLock 的可重入特性是怎样实现的呢?简单说一下,ReentrantLock 内部是通过 AQS 实现同步控制的,AQS 有一个变量 state 用于记录同步状态。初始情况下,state = 0,表示 ReentrantLock 目前处于解锁状态。如果有线程调用 lock 方法进行加锁,state 就由0变为1,如果该线程再次调用 lock 方法加锁,就让其自增,即 state++。线程每调用一次 unlock 方法释放锁,会让 state--。通过查询 state 的数值,即可知道 ReentrantLock 被重入的次数了。这就是可重复特性的大致实现流程。

2.3 公平与非公平

公平与非公平指的是线程获取锁的方式。公平模式下,线程在同步队列中通过 FIFO 的方式获取锁,每个线程最终都能获取锁。在非公平模式下,线程会通过“插队”的方式去抢占锁,抢不到的则进入同步队列进行排队。默认情况下,ReentrantLock 使用的是非公平模式获取锁,而不是公平模式。不过我们也可通过 ReentrantLock 构造方法ReentrantLock(boolean fair)调整加锁的模式。

既然既然有两种不同的加锁模式,那么他们有什么优缺点呢?答案如下:

公平模式下,可保证每个线程最终都能获得锁,但效率相对比较较低。非公平模式下,效率比较高,但可能会导致线程出现饥饿的情况。即一些线程迟迟得不到锁,每次即将到手的锁都有可能被其他线程抢了。这里再提个问题,为啥非公平模式抢了其他线程获取锁的机会,而整个程序的运行效率会更高呢?说实话,开始我也不明白。不过好在《Java并发编程实战》第13.3节 公平性(p232)说明了具体的原因,这里引用一下:

在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程 A 持有一个锁,并且线程 B 请求这个锁。由于这个线程已经被线程 A 持有,因此 B 将被挂起。当 A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果 C 也请求这个锁,那么 C 很有可能会在 B 被完全唤醒前获得、使用以及释放这个锁。这样的情况时一种“双赢”的局面:B 获得锁的时刻并没有推迟,C 更早的获得了锁,并且吞吐量也获得了提高。

上面的原因大家看懂了吗?下面配个图辅助说明一下:

如上图,线程 C 在线程 B 苏醒阶段内获取和使用锁,并在线程 B 获取锁前释放了锁,所以线程 B 可以顺利获得锁。线程 C 在抢占锁的情况下,仍未影响线程 B 获取锁,因此是个“双赢”的局面。

除了上面的原因外,《Java并发编程的艺术》在其5.3.2 公平与非公平锁的区别(p137)分析了另一个可能的原因。即公平锁线程切换次数要比非公平锁线程切换次数多得多,因此效率上要低一些。更多的细节,可以参考作者的论述,这里不展开说明了。

本节最后说一下公平锁和非公平锁的使用场景。如果线程持锁时间短,则应使用非公平锁,可通过“插队”提升效率。如果线程持锁时间长,“插队”带来的效率提升可能会比较小,此时应使用公平锁。

3. 源码分析

3.1 代码结构

前面说到 ReentrantLock 是基于 AQS 实现的,AQS 很好的封装了同步队列的管理,线程的阻塞与唤醒等基础操作。基于 AQS 的同步组件,推荐的使用方式是通过内部非 public 静态类继承 AQS,并重写部分抽象方法。其代码结构大致如下:

15256891997562

上图中,Sync是一个静态抽象类,继承了 AbstractQueuedSynchronizer。公平和非公平锁的实现类NonfairSyncFairSync则继承自 Sync 。至于 ReentrantLock 中的其他一些方法,主要逻辑基本上都在几个内部类中实现的。

3.2 获取锁

在分析 ReentrantLock 加锁的代码前,下来简单介绍一下 AQS 同步队列的一些知识。AQS 维护了一个基于双向链表的同步队列,线程在获取同步状态失败的情况下,都会被封装成节点,然后加入队列中。同步队列大致示意图如下:

在同步队列中,头结点是获取同步状态的节点。其他节点在尝试获取同步状态失败后,会被阻塞住,暂停运行。当头结点释放同步状态后,会唤醒其后继节点。后继节点会将自己设为头节点,并将原头节点从队列中移除。大致示意图如下:

介绍完 AQS 同步队列,以及节点线程获取同步状态的过程。下面来分析一下 ReentrantLock 中获取锁方法的源码,如下:

public void lock() {
    sync.lock();
}

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 这里的 lock 是抽象方法,具体的实现在两个子类中
    abstract void lock();
    
    // 省略其他无关代码
}

lock 方法的实现很简单,不过这里的 lock 方法只是一个壳子而已。由于获取锁的方式有公平和非公平之分,所以具体的实现是在NonfairSyncFairSync两个类中。那么我们继续往下分析一下这两个类的实现。

3.2.1 公平锁

公平锁对应的逻辑是 ReentrantLock 内部静态类 FairSync,我们沿着上面的 lock 方法往下分析,如下:

+--- ReentrantLock.FairSync.java
final void lock() {
    // 调用 AQS acquire 获取锁
    acquire(1);
}

+--- AbstractQueuedSynchronizer.java
/**
 * 该方法主要做了三件事情:
 * 1. 调用 tryAcquire 尝试获取锁,该方法需由 AQS 的继承类实现,获取成功直接返回
 * 2. 若 tryAcquire 返回 false,则调用 addWaiter 方法,将当前线程封装成节点,
 * 并将节点放入同步队列尾部
 * 3. 调用 acquireQueued 方法让同步队列中的节点循环尝试获取锁
 */
public final void acquire(int arg) {
    // acquireQueued 和 addWaiter 属于 AQS 中的方法,这里不展开分析了
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

+--- ReentrantLock.FairSync.java
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 获取同步状态
    int c = getState();
    // 如果同步状态 c 为0,表示锁暂时没被其他线程获取
    if (c == 0) {
        /*
 * 判断是否有其他线程等待的时间更长。如果有,应该先让等待时间更长的节点先获取锁。
 * 如果没有,调用 compareAndSetState 尝试设置同步状态。
 */ 
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 将当前线程设置为持有锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果当前线程为持有锁的线程,则执行重入逻辑
    else if (current == getExclusiveOwnerThread()) {
        // 计算重入后的同步状态,acquires 一般为1
        int nextc = c + acquires;
        // 如果重入次数超过限制,这里会抛出异常
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置重入后的同步状态
        setState(nextc);
        return true;
    }
    return false;
}

+--- AbstractQueuedSynchronizer.java
/** 该方法用于判断同步队列中有比当前线程等待时间更长的线程 */
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    /*
 * 在同步队列中,头结点是已经获取了锁的节点,头结点的后继节点则是即将获取锁的节点。
 * 如果有节点对应的线程等待的时间比当前线程长,则返回 true,否则返回 false
 */
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

ReentrantLock 中获取锁的流程并不是很复杂,上面的代码执行流程如下:

  1. 调用 acquire 方法,将线程放入同步队列中进行等待
  2. 线程在同步队列中成功获取锁,则将自己设为持锁线程后返回
  3. 若同步状态不为0,且当前线程为持锁线程,则执行重入逻辑

3.2.2 非公平锁

分析完公平锁相关代码,下面再来看看非公平锁的源码分析,如下:

+--- ReentrantLock.NonfairSync
final void lock() {
    /*
 * 这里调用直接 CAS 设置 state 变量,如果设置成功,表明加锁成功。这里并没有像公平锁
 * 那样调用 acquire 方法让线程进入同步队列进行排队,而是直接调用 CAS 抢占锁。抢占失败
 * 再调用 acquire 方法将线程置于队列尾部排队。
 */
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

+--- AbstractQueuedSynchronizer
/** 参考上一节的分析 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

+--- ReentrantLock.NonfairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

+--- ReentrantLock.Sync
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 获取同步状态
    int c = getState();
    
    // 如果同步状态 c = 0,表明锁当前没有线程获得,此时可加锁。
    if (c == 0) {
        // 调用 CAS 加锁,如果失败,则说明有其他线程在竞争获取锁
        if (compareAndSetState(0, acquires)) {
            // 设置当前线程为锁的持有线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果当前线程已经持有锁,此处条件为 true,表明线程需再次获取锁,也就是重入
    else if (current == getExclusiveOwnerThread()) {
        // 计算重入后的同步状态值,acquires 一般为1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 设置新的同步状态值
        setState(nextc);
        return true;
    }
    return false;
}

非公平锁的实现也不是很复杂,其加锁的步骤大致如下:

  1. 调用 compareAndSetState 方法抢占式加锁,加锁成功则将自己设为持锁线程,并返回
  2. 若加锁失败,则调用 acquire 方法,将线程置于同步队列尾部进行等待
  3. 线程在同步队列中成功获取锁,则将自己设为持锁线程后返回
  4. 若同步状态不为0,且当前线程为持锁线程,则执行重入逻辑

3.2.3 公平和非公平细节对比

如果大家之前阅读过公平锁和非公平锁的源码,会发现两者之间的差别不是很大。为了找出它们之间的差异,这里我将两者的对比代码放在一起,大家可以比较一下,如下:

从上面的源码对比图中,可以看出两种的差异并不大。那么现在请大家思考一个问题:在代码差异不大情况下,是什么差异导致了公平锁和非公平锁的产生呢?大家先思考一下,答案将会在下面展开说明。

在上面的源码对比图中,左边是非公平锁的实现,右边是公平锁的实现。从对比图中可看出,两者的 lock 方法有明显区别。非公平锁的 lock 方法会首先尝试去抢占设置同步状态,而不是直接调用 acquire 将线程放入同步队列中等待获取锁。除此之外,tryAcquire 方法实现上也有差异。由于非公平锁的 tryAcquire 逻辑主要封装在 Sync 中的 nonfairTryAcquire 方法里,所以我们直接对比这个方法即可。由上图可以看出,Sync 中的 nonfairTryAcquire 与公平锁中的 tryAcquire 实现上差异并不大,唯一的差异在第18行,这里我用一条红线标注了出来。公平锁的 tryAcquire 在第18行多出了一个条件,即!hasQueuedPredecessors()。这个方法的目的是判断是否有其他线程比当前线程在同步队列中等待的时间更长。有的话,返回 true,否则返回 false。比如下图:

node1 对应的线程比 node2 对应的线程在队列中等待的时间更长,如果 node2 线程调用 hasQueuedPredecessors 方法,则会返回 true。如果 node1 调用此方法,则会返回 false。因为 node1 前面只有一个头结点,但头结点已经获取同步状态,不处于等待状态。所以在所有处于等待状态的节点中,没有节点比它等待的更长了。理解了 hasQueuedPredecessors 方法的用途后,那么现在请大家思考个问题,假如把条件去掉对公平锁会有什么影响呢?答案在 lock 所调用的 acquire 方法中,再来看一遍 acquire 方法源码:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquire 方法先调用子类实现的 tryAcquire 方法,用于尝试获取同步状态,调用成功则直接返回。若调用失败,则应将线程插入到同步队列尾部,按照 FIFO 原则获取锁。如果我们把 tryAcquire 中的条件!hasQueuedPredecessors()去掉,公平锁将不再那么“谦让”,它将会像非公平锁那样抢占获取锁,抢占失败才会入队。若如此,公平锁将不再公平。

3.3 释放锁

分析完了获取锁的相关逻辑,接下来再来分析一下释放锁的逻辑。与获取锁相比,释放锁的逻辑会简单一些,因为释放锁的过程没有公平和非公平之分。好了,下面开始分析 unlock 的逻辑:

+--- ReentrantLock
public void unlock() {
    // 调用 AQS 中的 release 方法
    sync.release(1);
}

+--- AbstractQueuedSynchronizer
public final boolean release(int arg) {
    // 调用 ReentrantLock.Sync 中的 tryRelease 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        /*
 * 如果头结点的等待状态不为0,则应该唤醒头结点的后继节点。
 * 这里简单说个结论:
 * 头结点的等待状态为0,表示头节点的后继节点线程还是活跃的,无需唤醒
 */
        if (h != null && h.waitStatus != 0)
            // 唤醒头结点的后继节点,该方法的分析请参考我写的关于 AQS 的文章
            unparkSuccessor(h);
        return true;
    }
    return false;
}

+--- ReentrantLock.Sync
protected final boolean tryRelease(int releases) {
    /*
 * 用同步状态量 state 减去释放量 releases,得到本次释放锁后的同步状态量。
 * 当将 state 为 0,锁才能被完全释放
 */ 
    int c = getState() - releases;
    // 检测当前线程是否已经持有锁,仅允许持有锁的线程执行锁释放逻辑
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
        
    boolean free = false;
    // 如果 c 为0,则表示完全释放锁了,此时将持锁线程设为 null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    
    // 设置新的同步状态
    setState(c);
    return free;
}

重入锁的释放逻辑并不复杂,这里就不多说了。

4.总结

本文分析了可重入锁 ReentrantLock 公平与非公平获取锁预计释放锁原理,并与 synchronized 关键字进行了类比。总体来说,ReentrantLock 的原理在熟悉 AQS 原理的情况下,理解并不是很复杂。ReentrantLock 是大家经常使用的一个同步组件,还是很有必要去弄懂它的原理的。

好了,本文到这里就结束了。谢谢大家的阅读,再见。

 

分享到:
评论

相关推荐

    手写可重入锁

    可重入锁,也称为递归锁,是Java并发编程中的一个重要概念,主要在`java.util.concurrent.locks.ReentrantLock`类中实现。这个概念对于理解多线程环境下的同步控制至关重要,尤其对于初学者来说,掌握其原理和用法...

    Java并发控制机制ReentrantLock详解与synchronized对比分析

    内容概要:本文深入探讨了Java中的并发控制机制,重点讲解了ReentrantLock和synchronized的特点及其背后的实现原理。通过对两者的特性进行对比,详细解析了ReentrantLock在灵活性、公平性和中断响应等方面的优点。并...

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    ReentrantLock是一种基于AQS框架实现的可重入锁。它可以显式地控制锁的获取和释放,并提供了公平性和非公平性两种获取策略。此外,ReentrantLock还支持尝试获取锁的功能,即在指定时间内尝试获取锁而不阻塞。 #### ...

    synchronized ReentrantLock volatile Atomic 原理分析.docx

    本文将深入探讨四种关键的并发控制机制:synchronized关键字、ReentrantLock(可重入锁)、volatile关键字以及Atomic类的原理与应用。 ### 1. synchronized关键字 `synchronized`关键字是Java提供的内置锁,用于...

    Java 多线程与并发(11-26)-JUC锁- ReentrantLock详解.pdf

    Java中的ReentrantLock是Java并发包(java.util.concurrent.locks)中的一个高级锁,它是可重入的,意味着一个线程可以多次获取同一锁。在深入ReentrantLock之前,我们首先需要了解Java并发编程的基础,特别是Java...

    Java并发系列之ReentrantLock源码分析

    Java并发系列之ReentrantLock源码分析 ReentrantLock是Java 5.0中引入的一种新的加锁机制,它实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。ReentrantLock的底层实现是通过AQS来实现多线程同步...

    synchronized锁原理分析(一、从Java对象头看synchronized锁的状态)

    本文将深入探讨synchronized锁的实现原理,特别是从Java对象头的角度来分析其工作机制。 首先,我们需要理解synchronized锁住的是对象,而不是代码块。例如,当我们使用`synchronized (o)`对一个对象o进行加锁时,...

    第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()1

    ReentrantLock是由Java提供的可重入互斥锁,支持公平锁和非公平锁两种模式。非公平锁的特性是获取锁的线程不论先后,都有机会获得锁,而公平锁则遵循先进先出的原则,保证线程按照请求锁的顺序进行。 2. **...

    3.1.4.AQS底层原理分析1

    【3.1.4.AQS底层原理分析1】 在Java并发编程中,AbstractQueuedSynchronizer(AQS)是一个核心的同步组件,用于构建锁和同步器的基础框架。AQS是一个抽象类,它提供了线程同步的基本机制,包括线程的排队、等待和...

    java并发编程面试题

    java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换?...ReentrantLock(重入锁)实现原理与公平锁非公平锁区别什么是可重入锁(ReentrantLock)? ThreadLocal内存泄漏分析与

    面向Java锁机制的字节码自动重构框架.zip

    本文将深入探讨Java锁机制,并基于提供的"面向Java锁机制的字节码自动重构框架"来讨论其背后的原理和应用。 在Java中,锁主要分为内置锁(也称为监视器锁)和显式锁。内置锁是通过synchronized关键字实现的,它提供...

    java 中volatile和lock原理分析

    《Java中volatile与锁原理分析》 在多线程编程中,确保数据的一致性和可见性是至关重要的。Java提供了两种主要的机制来实现这一目标:volatile关键字和锁。本文将深入探讨这两种机制的原理,以及它们在实际编程中的...

    java并发编程专题(五)----详解(JUC)ReentrantLock

    总结,Java并发编程中的ReentrantLock是一个强大且灵活的锁机制,提供了可重入、公平/非公平选择以及条件变量等功能。它使得开发者能够更好地控制并发环境中的线程同步,提高程序的效率和安全性。在实际开发中,根据...

    Java分布式应用学习笔记06浅谈并发加锁机制分析

    `ReentrantLock`是一种可重入的互斥锁,支持公平和非公平两种模式。它内部有一个`Sync`类,继承自`AbstractQueuedSynchronizer`,并且有两个子类分别实现了公平和非公平锁的逻辑。 - **非公平锁** (`NonfairSync`):...

    基于JDK源码解析Java领域中的并发锁之设计与实现.pdf

    典型的Lock实现如ReentrantLock,它支持公平锁和非公平锁,以及可重入和可中断的特性。 五、ReadWriteLock接口的设计与实现 ReadWriteLock接口代表读写锁,它允许多个读取者同时访问资源,但在写入时确保互斥。典型...

    图灵Java高级互联网架构师第6期并发编程专题笔记.zip

    09-深入理解AQS之独占锁ReentrantLock源码分析-fox 10-深入理解AQS之Semaphorer&CountDownLatch&CyclicBarrie详解-fox 11-深入理解AQS之CyclicBarrier&ReentrantReadWriteLock详解-fox 12-深入理解AQS之...

    21 更高级的锁—深入解析Lock.pdf

    然而,从Java 5.0开始,JDK引入了ReentrantLock,这是一种更强大的可重入锁,提供了比synchronized更丰富的功能。ReentrantLock不仅具备与synchronized相同的互斥和可重入特性,还提供了更多的灵活性和控制性。 **...

    java并发容器CopyOnWriteArrayList实现原理及源码分析

    Java并发容器CopyOnWriteArrayList实现原理及源码分析 Java并发容器CopyOnWriteArrayList是Java并发包中提供的一个并发容器,实现了线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现。...

    Java synchronized关键字和Lock接口实现原理

    ReentrantLock 类是一个重入锁,它可以确保在同一时间只有一个线程可以执行该方法或者代码块。ReentrantLock 类的实现原理是基于操作系统的互斥锁(Mutex Lock)。 ReentrantLock 类的实现原理 ReentrantLock 类的...

    Java多线程同步机制的应用分析.pdf

    使用synchronized关键字可以实现线程之间的互斥访问,而使用ReentrantLock类可以实现线程之间的可重入锁。 在实际应用中,Java多线程同步机制可以用于解决多种问题,例如售票系统、银行系统等。通过使用同步机制,...

Global site tag (gtag.js) - Google Analytics