面试10000次依然会问的【ReentrantLock】,你还不会?

news2024/11/29 2:48:59

引言

在并发编程的世界中,ReentrantLock扮演着至关重要的角色。它是一个实现了重入特性的互斥锁,提供了比synchronized关键字更加灵活的锁定机制。ReentrantLock属于java.util.concurrent.locks包,是Java并发API的一部分。

与传统的synchronized方法或代码块相比,ReentrantLock提供了更丰富的功能,如可中断的锁获取操作、尝试非阻塞地获取锁、公平锁以及支持多个条件变量等。

在多线程环境下,ReentrantLock能够确保线程在访问共享资源时的互斥性,从而避免了资源的竞争和潜在的数据不一致性问题。通过其提供的锁定机制,开发者可以构建出更加健壮和高效的并发应用程序。

特别是在读多写少的场景中,ReentrantLock的读写锁(ReentrantReadWriteLock)能够显著提高程序的性能,因为它允许多个线程同时对资源进行读取,而写入则需要独占访问。

ReentrantLock不仅加强了程序的并发性能,也为复杂的同步策略提供了可能,是并发编程中不可或缺的工具之一。

ReentrantLock与WriteLock的区别

ReentrantLock和WriteLock都是Java并发编程中用于控制多线程访问共享资源的锁机制。ReentrantLock是一个完全独立的锁,提供了比synchronized关键字更灵活的锁定操作,它支持公平锁和非公平锁,能够响应中断,还能够尝试非阻塞地获取锁。

而WriteLock是ReentrantReadWriteLock的一部分,它专门用于写操作,确保了写操作的原子性和可见性。在获取WriteLock时,必须确保没有其他线程正在读或写,这意味着WriteLock在获取锁的过程中需要同时考虑读锁和写锁的状态,而ReentrantLock则只需要考虑自身的状态。

ReentrantLock支持锁的重入,即同一个线程可以多次获取同一把锁,而WriteLock作为读写锁的一部分,也支持锁的重入。

ReentrantLock与Semaphore的区别

ReentrantLock和Semaphore虽然都是并发编程中的同步工具,但它们的用途和工作方式有所不同。ReentrantLock是一种独占锁,它可以由同一个线程多次获取,用于实现临界区的互斥访问。ReentrantLock的独占性意味着在锁被释放之前,其他所有请求这个锁的线程都会被阻塞。

相比之下,Semaphore是一个计数信号量,它不是为了互斥访问而设计的,而是用来限制同时访问某一组资源的线程数量。Semaphore可以配置为公平或非公平,而ReentrantLock也提供了这样的配置选项。Semaphore通常用于控制资源池,例如限制最大的数据库连接数。Semaphore允许多个线程同时访问资源,但是一旦达到最大许可数,其他线程则需要等待,直到一个正在访问资源的线程释放了许可。

在使用场景上,ReentrantLock更适合于对象级别的互斥,而Semaphore适用于控制对应用程序范围内资源的访问。

锁的获取与释放

ReentrantLock作为一个独立的独占锁,其获取与释放锁的机制是通过一个叫做抽象队列同步器(AQS)的框架来实现的。当一个线程尝试获取锁时,它会调用AQS的独占获取方法,这个过程可以通过sync.acquire(1)来实现。如果锁未被占用,这个线程将成功获取锁并持有。如果锁已被其他线程持有,尝试获取锁的线程将会被加入到一个等待队列中,并在锁被释放时按照一定的策略(如公平或非公平)被唤醒。

释放锁的过程则是通过调用tryRelease(int releases)方法来实现的,这个方法会在同步状态减至零时完成。在ReentrantLock中,每个获取锁的操作都会使同步状态增加,而每个释放锁的操作都会使其减少。当同步状态回到零时,表示锁已经完全释放,等待队列中的其他线程可以尝试获取锁。

重入性的实现原理

ReentrantLock的重入性是指线程可以重复获取它已经持有的锁。这一特性是通过AQS中的同步状态来实现的。当线程第一次获取锁时,AQS会记录下锁的持有者,并将同步状态设置为1。如果当前线程再次尝试获取这个锁,它会检查自己是否为当前的持有者。如果是,它将直接增加同步状态而不是进入等待队列。

在ReentrantLock的实现中,同步状态的增加和减少代表了锁的获取和释放次数。只有当同步状态减至零时,锁才被认为是完全释放的,这时其他线程才有机会获取锁。这种设计允许了同一个线程在没有完全释放锁的情况下,多次进入由这个锁保护的代码区域,从而实现了锁的重入性。

通过这种方式,ReentrantLock确保了在多线程环境下,同一个线程可以安全地重复进入锁定的代码区,而不会导致死锁。同时,这也意味着线程在每次进入时都必须记得释放锁,否则其他线程将永远无法获取到锁,从而导致系统的不稳定。

读锁和写锁的实现机制

ReentrantReadWriteLock提供了两种锁:读锁(ReadLock)和写锁(WriteLock)。这两种锁的实现机制是为了解决读多写少的并发问题,提高系统性能。

读锁是共享的,允许多个线程同时访问共享资源,但在写线程访问时,所有读线程和其他写线程都会被阻塞。读锁的获取和释放是通过AQS(AbstractQueuedSynchronizer)框架中的同步状态来实现的。当一个线程尝试获取读锁时,如果没有线程持有写锁(即写状态为0),则通过CAS(Compare-And-Swap)操作增加同步状态中的读状态,表示读锁的获取。释放读锁时,同步状态中的读状态相应减少。

写锁是独占的,一次只允许一个线程进行写入操作。当一个线程尝试获取写锁时,它需要检查是否存在其他写锁或读锁。如果没有其他线程持有读锁或写锁,该线程通过AQS独占模式尝试获取锁。获取写锁的过程中,如果有线程持有读锁或其他写锁,当前线程将无法获取写锁,必须等待。

在实现缓存系统时,使用ReentrantReadWriteLock可以提高缓存的读取效率,同时保证写入操作的安全性。例如,当缓存失效时,需要获取写锁来更新缓存,更新后再降级为读锁以允许其他线程读取新缓存。

锁降级的操作和原理

锁降级是指在持有写锁的情况下,先获取读锁,然后释放写锁的过程。这样做可以保持数据的可见性,即使在锁被降级后,其他线程也无法写入数据,因为读锁仍然被持有。Java中的ReentrantReadWriteLock支持锁降级,但不支持锁升级(即在持有读锁的情况下直接获取写锁)。

锁降级的主要用途是在需要保持数据读取的一致性,同时减少锁竞争的场景下。例如,在一个缓存系统中,大部分操作是读取数据,只有在数据失效时才需要写入。使用读写锁可以在不牺牲数据一致性的前提下,提高系统的并发读取性能。

在锁降级的操作中,首先获取写锁以确保对共享数据的独占访问。在修改数据后,我们在释放写锁之前获取读锁,这样即使写锁被释放,其他线程也无法获取写锁来修改数据,但可以获取读锁来读取数据。这就完成了锁降级的过程。最后,在使用完数据后释放读锁。

公平性与性能

ReentrantLock提供了两种锁的获取策略:公平锁和非公平锁。公平锁意味着锁的分配将按照线程请求的顺序来进行,确保了等待时间最长的线程最先获得锁,从而避免了饥饿现象。然而,公平锁可能会导致较多的性能开销,因为维护一个有序队列并在每次锁释放时进行线程调度,会增加额外的开销。

相比之下,非公平锁则不保证请求锁的顺序,允许插队,这通常会导致更高的吞吐量。因为非公平锁减少了线程之间的切换,从而减少了上下文切换的成本。但是,这种策略可能会导致新的线程饥饿,尤其是在高负载时。在实际应用中,非公平锁通常是默认的选择,因为它们在大多数情况下提供了更好的性能。

锁的状态管理

ReentrantLock通过内部类Sync(继承自AbstractQueuedSynchronizer,简称AQS)来管理锁的状态。AQS使用一个int类型的状态变量来表示锁的状态,对于ReentrantLock而言,状态的值表示锁的持有次数。当线程请求锁时,AQS会尝试通过CAS(Compare-And-Swap)操作来改变这个状态值,如果成功,则表示线程获取了锁。

当锁被释放时,状态值相应地减少。当状态值降到0时,表示锁完全释放。由于ReentrantLock是可重入的,同一个线程可以多次获得锁,每次获取锁都会使状态值增加,每次释放锁都会使状态值减少。AQS提供了一种机制来保证状态的安全更新,同时也提供了队列机制来管理那些未能成功获取锁的线程。

通过这种方式,ReentrantLock确保了锁状态的准确性和线程安全性,同时也支持了锁的高级特性,如条件变量(Condition),它们允许线程在某些条件下挂起和唤醒。

实现一个简单的ReentrantReadWriteLock缓存系统

ReentrantReadWriteLock是一种读写锁,它允许多个线程同时读取数据,但是在写入数据时,只允许一个线程进行操作。这种锁机制非常适合实现缓存系统,因为缓存系统通常面临大量的读操作和少量的写操作。下面是一个简单的使用ReentrantReadWriteLock实现的缓存系统的代码示例:

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;

public class CacheWithReadWriteLock {
    private final Map<String, Object> cacheMap = new HashMap<>();
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    // 获取缓存中的值
    public Object get(String key) {
        readWriteLock.readLock().lock(); // 获取读锁
        try {
            return cacheMap.get(key);
        } finally {
            readWriteLock.readLock().unlock(); // 释放读锁
        }
    }
    
    // 放入缓存中的值
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock(); // 获取写锁
        try {
            cacheMap.put(key, value);
        } finally {
            readWriteLock.writeLock().unlock(); // 释放写锁
        }
    }
    
    // 其他缓存操作...
}

在这个示例中,我们定义了一个CacheWithReadWriteLock类,它内部使用了一个HashMap来存储缓存数据,以及一个ReentrantReadWriteLock来控制对缓存的并发访问。当需要读取缓存时,我们获取读锁,这允许多个线程同时读取缓存;当需要写入缓存时,我们获取写锁,这确保了只有一个线程能够写入数据,从而保证了数据的一致性。

写锁的状态减少和释放

写锁是一种独占锁,当线程完成写操作后,它需要释放锁,以便其他线程可以访问数据。在ReentrantReadWriteLock中,写锁的释放通常涉及到状态的减少。这是因为ReentrantReadWriteLock支持锁的重入,即同一个线程可以多次获取同一个锁,每次获取锁时都会增加状态计数,每次释放锁时都会减少状态计数。

以下是一个简化的写锁释放过程的代码示例:

public class CacheWithReadWriteLock {
    // ...(其他代码)

    // 更新缓存并释放写锁
    public void updateCache(String key, Object value) {
        readWriteLock.writeLock().lock(); // 获取写锁
        try {
            // 更新缓存数据
            cacheMap.put(key, value);
        } finally {
            // 在释放写锁前获取读锁,实现锁降级
            readWriteLock.readLock().lock();
            readWriteLock.writeLock().unlock(); // 释放写锁,此时读锁仍然被持有
            // 确保数据可见性,允许其他线程读取更新后的数据
            try {
                // 可以进行一些只需要读锁的操作
            } finally {
                readWriteLock.readLock().unlock(); // 最终释放读锁
            }
        }
    }
}

在这个示例中,updateCache方法首先获取写锁来更新缓存。在更新操作完成后,它在释放写锁之前获取了读锁,这是一种锁降级的操作,它允许线程在保持数据可见性的同时,减少锁的竞争。最后,线程释放了读锁,使得其他线程可以安全地读取更新后的数据。

总结

ReentrantLock 是 Java 并发编程中的一个高级同步机制,它提供了比传统 synchronized 方法和语句更丰富的操作。在现代多线程编程中,ReentrantLock 的关键特性使其成为管理复杂同步需求的强大工具。

ReentrantLock 支持重入性,即线程可以重复获取已经持有的锁,这对于递归调用或者其他需要多次加锁的场景非常有用。其次,ReentrantLock 提供了公平锁和非公平锁的选择,公平锁可以按照线程请求锁的顺序来分配锁,而非公平锁则可能允许后请求的线程先获得锁,这在某些情况下可以减少线程切换,提高效率。

ReentrantLock 还提供了条件变量(Condition),这允许线程在某些条件不满足时挂起,等待特定条件的发生再继续执行,这比 Object 的 wait/notify 机制提供了更细粒度的控制。

在性能方面,ReentrantLock 提供的锁机制通常比 synchronized 更加灵活和高效,尤其是在高竞争环境下。它允许开发者通过精细的锁管理策略来优化并发性能,比如限制锁的范围、分离读写操作等。

ReentrantLock 的这些特性使其在多线程编程中非常有用,尤其是在需要高度并发控制和灵活性的应用程序中。通过合理使用 ReentrantLock,开发者可以构建出既安全又高效的并发应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1172238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用CodeceptJS、Playwright和GitHub Actions构建端到端测试流水线

介绍 端到端测试是软件开发的一个重要方面&#xff0c;因为它确保系统的所有组件都能正确运行。CodeceptJS是一个高效且强大的端到端自动化框架&#xff0c;与Playwright 结合使用时&#xff0c;它成为自动化Web、移动甚至桌面 (Electron.js) 应用程序比较好用的工具。 在本文中…

2023-11 | 短视频批量下载/爬取某个用户的所有视频 | Python

这里以鞠婧祎的个人主页为demo https://www.douyin.com/user/MS4wLjABAAAACV5Em110SiusElwKlIpUd-MRSi8rBYyg0NfpPrqZmykHY8wLPQ8O4pv3wPL6A-oz 【2023-11-4 23:02:52 星期六】可能后面随着XX的调整, 方法不再适用, 请注意 找到接口 找到https://www.douyin.com/aweme/v1/web/…

rust入门基础案例:猜数字游戏

案例出处是《Rust权威指南》&#xff0c;书中有更加详细的解释。从这个例子中&#xff0c;我们可以了解到 rust 的两个操作&#xff1a; 如何从控制台读取用户输入rust 如何生成随机数 代码格式化 编译器可在保存时对代码做格式化处理&#xff0c;底层调用 rustfmt 来实现&a…

【gpt redis】原理篇

用的黑马程序员redis课程的目录&#xff0c;但是不想听讲了。后续都是用gpt文档获取的。 1.课程介绍(Av766995956,P145) 2.Redis数据结构-动态字符串(Av766995956,P146) sds 1M是个界限 其实他是个由c语言实现的结构体 有这么几个参数 len alloc flag char[] len是实际长度 …

【漏洞复现】Apache Log4j Server 反序列化命令执行漏洞(CVE-2017-5645)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证 1.5、深度利用1、反弹Shell 说明内容漏洞编号CVE-2017-5645漏洞名称Log4j Server …

Python 中的 Gzip 解压

我们将介绍Python中的gzip解压。 我们还将介绍如何使用 gzip 解压缩来解压缩压缩内容。 Python 中的 Gzip 解压 Python 中构建了许多用于压缩和解压缩目的的库&#xff0c;但我们将介绍 Gzip 库。 它是一种流行的数据压缩工具。 我们可以使用 gzip 通过将数据编码为人类无法读…

字符型液晶显示器LCD 1602的显示控制(Keil+Proteus)

前言 趁机把LCD 1602的实验完成了&#xff0c;那个电路图有几个地方没弄懂&#xff0c;但是去掉也没有报错&#xff0c;就没管了。 LCD1602_百度百科 (baidu.com)https://baike.baidu.com/item/LCD1602/6014393?frge_ala LCD1602液晶显示屏通过电压来改变填充在两块平行板之…

Python算法例5 x的n次幂

1. 问题描述 实现函数Pow&#xff08;x&#xff0c;n&#xff09;&#xff0c;计算并返回x的n次幂。 2. 问题示例 Pow&#xff08;2.1&#xff0c;2&#xff09;4.41&#xff1b;Pow&#xff08;0&#xff0c;1&#xff09;0&#xff1b;Pow&#xff08;1&#xff0c;0&…

C++——类和对象之运算符重载

运算符重载 本章思维导图&#xff1a; 注&#xff1a;本章思维导图对应的xmind文件和.png文件都已同步导入至”资源“ 文章目录 运算符重载[toc] 1. 运算符重载的意义2. 函数的声明2.1 声明运算符重载的注意事项 3. 函数的调用4. const成员函数4.1 const成员函数的声明4.2 注意…

C++查漏补缺与新标准(C++20,C++17,C++11)02 C++快速回顾(二)

本内容参考C20高级编程 C风格的数组 //形如 int myArray[3]{2};一个比较新颖的获取C风格数组大小的函数std::size()&#xff0c;返回size_t类型&#xff08;在中定义的无符号整数&#xff09; #include <iostream> using namespace std;int main() {int myArray[5] {…

UML---用例图

UML–用例图 0.用例图简介 用例图是一种UML&#xff08;统一建模语言&#xff09;的图形化表示方法&#xff0c;用于描述系统的功能和行为。它可以帮助系统分析师和开发人员理解系统的需求&#xff0c;用例图由参与者、用例和它们之间的关系组成。 1.用例图的组成部分 系统…

【RabbitMQ】RabbitMQ 消息的可靠性 —— 生产者和消费者消息的确认,消息的持久化以及消费失败的重试机制

文章目录 前言&#xff1a;消息的可靠性问题一、生产者消息的确认1.1 生产者确认机制1.2 实现生产者消息的确认1.3 验证生产者消息的确认 二、消息的持久化2.1 演示消息的丢失2.2 声明持久化的交换机和队列2.3 发送持久化的消息 三、消费者消息的确认3.1 配置消费者消息确认3.2…

订单业务和系统设计(一)

一、背景简介 订单其实很常见&#xff0c;在电商购物、外卖点餐、手机话费充值等生活场景中&#xff0c;都能见到它的影子。那么&#xff0c;一笔订单的交易过程是什么样子的呢&#xff1f;文章尝试从订单业务架构和产品功能流程&#xff0c;描述对订单的理解。 二、订单业务…

飞书开发学习笔记(二)-云文档简单开发练习

飞书开发学习笔记(二)-云文档简单开发练习 一.云文档飞书开发环境API 首先还是进入开放平台 飞书开放平台&#xff1a;https://open.feishu.cn/app?langzh-CN 云文档相关API都在“云文档”目录中&#xff0c;之下又有"云空间",“文档”&#xff0c;“电子表格”&a…

LLM系列 | 26:阿里千问Qwen模型解读、本地部署

引言 简介 预训练 数据来源 预处理 分词 模型设计 外推能力 模型训练 实验结果 部署实测 对齐 监督微调(SFT) RM 模型 强化学习 对齐结果(自动和人工评估) 自动评估 人工评估 部署实测 总结 引言 人生自是有情痴&#xff0c;此恨不关风与月。 ​ 今天这篇小…

从零开始:开发你的第一个抖音小程序

抖音小程序提供了独特的机会&#xff0c;能够让你将自己的创意和内容传播给数百万的抖音用户。本文将带你走一趟开发抖音小程序的旅程&#xff0c;从零开始&#xff0c;无需编程经验。你将了解到如何准备开发环境、创建你的第一个小程序&#xff0c;以及如何将它发布到抖音平台…

辅助驾驶功能开发-功能规范篇(22)-9-L2级辅助驾驶方案功能规范

1.3.7.2 行人、骑行者(横向)AEB 系统 1.3.7.2.1 状态机 1.3.7.2.2 信号需求列表 同 1.3.2.1.2。 1.3.7.2.3 系统开启关闭 同 1.3.2.1.3。 触发横向 AEB 的目标包括横向运动的行人、骑行者(包括自行车、摩托车、电瓶车和平衡车上的行人)。 1.3.7.2.4 制动预填充 制动系统…

Global-aware siamese network for change detection on remote sensing images

遥感图像中的变化检测是以有效的方式识别观测变化的最重要的技术选择之一。CD具有广泛的应用&#xff0c;如土地利用调查、城市规划、环境监测和灾害测绘。然而&#xff0c;频繁出现的类不平衡问题给变化检测应用带来了巨大的挑战。为了解决这个问题&#xff0c;我们开发了一种…

Spring Boot 整合SpringSecurity和JWT和Redis实现统一鉴权认证

&#x1f4d1;前言 本文主要讲了Spring Security文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&#xff1a;努力…

VPN网络环境下 本地客户端能连上mysql 本地启服务连不上mysql的原因

背景 公司mysql使用的是华为云RDS&#xff0c;由于要做一些测试验证&#xff0c;需要本地通过VPN直连华为RDS节点&#xff1b;找运维配置好网络后&#xff0c;本地 telnet 内网ip 3306 以及通过navicat客户端都能正常连接数据库&#xff1b;但是本地启动的服务就是连接不上。问…