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

引言

在并发编程的世界中,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.hqwc.cn/news/160518.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android ConstraintLayout分组堆叠圆角ShapeableImageView

Android ConstraintLayout分组堆叠圆角ShapeableImageView <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"…

STM32WB55开发(6)----FUS更新

STM32WB55开发.6--FUS更新 概述视频教学硬件准备存储器映射FLASH安全区设置SRAM安全区设置通过USB进行下载注意事项 概述 在 STM32WB 微控制器中&#xff0c;FUS&#xff08;Firmware Upgrade Services&#xff09;是用于固件升级的一种服务。这项服务可以让你更新设备上的无…

GAMP源码阅读:PPP中的模型改正:天线相位中心、天线相位缠绕、潮汐、地球自转效应、引力延迟

原始 Markdown文档、Visio流程图、XMind思维导图见&#xff1a;https://github.com/LiZhengXiao99/Navigation-Learning 文章目录 一、卫星天线相位中心改正1、原理2、文件读取3、setpcv()&#xff1a;设置天线参数4、satantoff()&#xff1a;卫星 PCO 改正5、satantpcv()&…

红黑树——插入底层实现【C++】面试重灾区!!

目录 前言 一&#xff0c;概念 定义 二&#xff0c;insert 情况一&#xff1a; 情况二&#xff1a; 情况三&#xff1a; insert代码 三&#xff0c; 红黑树验证(面试题) 产生随机数验证 每日一图区&#xff1a; 前言 AVL树是一棵绝对平衡的二叉搜索树&#xff0c;其…

【el-cascader-panel】组件el-cascader-panel使用踩坑

需求背景&#xff1a;角色管理资源&#xff0c;资源返回树形结构数据&#xff0c;左侧树形展示列表可查询&#xff0c;右侧勾选资源权限平铺。 本身组件不支持全选&#xff0c;所以增加了全选按钮。覆写了级联面板宽度。可传只勾选code或者顺序当前节点二维数组列表。 效果 因…

消息中间件-RabbitMQ介绍

一、基础知识 1. 什么是RabbitMQ RabbitMQ是2007年发布&#xff0c;是一个在AMQP(高级消息队列协议)基础上完成的&#xff0c;简称MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法&#xff0c;由Erlang&#xff08;专门针对于大…

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/…

WebGL:基础练习 / 简单学习 / demo / canvas3D

一、前置内容 canvas&#xff1a;理解canvas / 基础使用 / 实用demo-CSDN博客 WebGL&#xff1a;开始学习 / 理解 WebGL / WebGL 需要掌握哪些知识 / 应用领域 / 前端值得学WebGL吗_webgl培训-CSDN博客 二、在线运行HTML 用来运行WebGL代码&#xff0c;粘贴--运行&#xff…

【JMeter】插件管理工具

1. 官方下载地址 Documentation :: JMeter-Plugins.org 2.安装 将该插件的jar包移动到lib/ext下 3.重启JMeter就可以看到插件管理器 4. 安装&#xff0c;更新&#xff0c;删除插件 安装插件 删除插件 更新插件

【Java】三种方案实现 Redis 分布式锁

序言 setnx、Redisson、RedLock 都可以实现分布式锁&#xff0c;从易到难得排序为&#xff1a;setnx < Redisson < RedLock。一般情况下&#xff0c;直接使用 Redisson 就可以啦&#xff0c;有很多逻辑框架的作者都已经考虑到了。 方案一&#xff1a;setnx 1.1、简单实…

九、W5100S/W5500+RP2040树莓派Pico<SNTP 获取网络时间>

文章目录 1 前言2 协议简介2.1 什么是SNTP2.2 SNTP的优点2.3 SNTP原理2.4 应用场景 3 WIZnet以太网芯片4 SNTP网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着科技的不断进步和应用需求的不断变…

虚拟dom及diff算法之 —— h函数和diff函数

新虚拟dom和老虚拟dom进行diff算法&#xff08;精细化比较&#xff09;&#xff0c;算出如何最小量更新&#xff0c;最后反映到真实dom上 diff是发生在虚拟dom上的 模板编译 虚拟dom如何产生 - 渲染函数&#xff08;h函数&#xff09; h函数产生虚拟节点&#xff08;vnode&a…