面试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.rhkb.cn/news/180651.html

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

相关文章

隐私保护多领域推荐的紧密度共聚类联邦概率偏好分布模型

论文链接 Federated Probabilistic Preference Distribution Modelling with Compactness Co-Clustering for Privacy-Preserving Multi-Domain Recommendation 引言 这篇论文提出的概率偏好分布是通过使用高斯分布来表示用户和项目的偏好。在论文中&#xff0c;作者提出了一…

10.MySQL事务(上)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 前言&#xff1a; 是什么&#xff1f; 为什么? 怎么做&#xff1f; 前言&#xff1a; 本篇文章将会说明什么是事务&#xff0c;为什么会出现事务&#xff1f;事务是怎么做的&#xff1f; 是什么&#xff1f; 我…

Nginx反向代理和负载均衡

文章目录 前言一、Nginx介绍二、Nginx的作用1.正向代理2.反向代理3.负载均衡之轮询4.负载均衡之加权轮询 三、Nginx安装1.Nginx下载2.启动Nginx3.检查Nginx是否启动成功4.配置监听5.关闭nginx 前言 比如公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&a…

ElementuiPlus的table组件实现行拖动与列拖动

借助了插件sortablejs。这种方法只适合做非树状table。如果想实现树状table&#xff0c;并且可拖动。可以试一下aggridVue3这个插件 <template><div class"draggable" style"padding: 20px"><el-table row-key"id" :data"t…

计算机组成与结构-计算机体系结构

计算机体系结构 指令系统 Flynn分类法 SISD&#xff08;单指令流单数据流&#xff09; 结构 控制部分&#xff1a;一个处理器&#xff1a;一个主存模块&#xff1a;一个 代表 单处理器系统 SIMD&#xff08;单指令流多数据流&#xff09; 结构 控制部分&#xff1a;一个处理…

CleanMyMac2024破解版如何下载?

CleanMyMac作为一款专业的苹果电脑清理软件&#xff0c;它不仅仅能单纯的卸载不用、少用的应用&#xff0c;同时还支持&#xff1a;1、清理应用程序的数据文件&#xff0c;将应用重置回初始状态&#xff0c;减少空间占用&#xff1b;2、自动检查应用更新&#xff0c;保持应用的…

Python画图之动态爱心

Python画出动态爱心&#xff08;有趣小游戏&#xff09; 一、效果图二、Python代码 一、效果图 二、Python代码 import random from math import sin, cos, pi, log from tkinter import *CANVAS_WIDTH 640 # 画布的宽 CANVAS_HEIGHT 480 # 画布的高 CANVAS_CENTER_X CANV…

【PID专题】MATLAB如何实现PID?

MATLAB是一种非常强大的工具&#xff0c;用于实现和分析PID&#xff08;比例-积分-微分&#xff09;控制器。在MATLAB中&#xff0c;您可以使用控制系统工具箱来设计、模拟和调整PID控制系统。 以下是一般步骤&#xff0c;演示如何在MATLAB中实现PID控制&#xff1a; 1. 打开MA…

PHP进销存ERP系统源码

PHP进销存ERP系统源码 系统介绍&#xff1a; 扫描入库库存预警仓库管理商品管理供应商管理。 1、电脑端手机端&#xff0c;手机实时共享&#xff0c;手机端一目了然。 2、多商户Saas营销版 无限开商户&#xff0c;用户前端自行注册&#xff0c;后台管理员审核开通 3、管理…

【服务器】Java连接redis及使用Java操作redis、使用场景

一、Java连接redis-No-SQL 1、导入依赖 在你的项目里面导入redis的pom依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version> </dependency> 2、连接redis 连接redis //…

Redis-使用java代码操作Redis

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; ​ &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Linux》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这…

HarmonyOS UI 开发

引言 HarmonyOS 提供了强大的 UI 开发工具和组件&#xff0c;使开发者能够创建吸引人的用户界面。本章将详细介绍在 HarmonyOS 中应用 JS、CSS、HTML&#xff0c;HarmonyOS 的 UI 组件以及如何自定义 UI 组件。 目录 JS、CSS、HTML 在 HarmonyOS 中的应用HarmonyOS 的 UI 组…

【STM32】基于HAL库建立自己的低功耗模式配置库(STM32L4系列低功耗所有配置汇总)

【STM32】基于HAL库建立自己的低功耗模式配置库&#xff08;STM32L4系列低功耗所有配置汇总&#xff09; 文章目录 低功耗模式&#xff08;此章节可直接跳过&#xff09;低功耗模式简介睡眠模式停止模式待机模式 建立自己的低功耗模式配置库通过结构体的方式来进行传参RTC配置…

将Bean注入Spring容器的五种方式

将bean放入Spring容器中有哪些方式&#xff1f; 我们知道平时在开发中使用Spring的时候&#xff0c;都是将对象交由Spring去管理&#xff0c;那么将一个对象加入到Spring容器中&#xff0c;有哪些方式呢&#xff0c;下面我就来总结一下 1、Configuration Bean 这种方式其实也是…

数据结构 | 单链表专题【详解】

数据结构 | 单链表专题【详解】 文章目录 数据结构 | 单链表专题【详解】链表的概念及结构单链表的实现头文件打印尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除pos节点删除pos之后的节点销毁链表 顺序表遗留下来的问题 中间/头部的插⼊删除&#xff…

vue3后台管理系统之实现分页功能

例子&#xff1a;用户 请求格式 返回数据类型 {"code": 200,"message": "获取所有用户成功","total": 19,"totalPages": 2,"currentPage": 1,"data": [{"id": 1,"username": &qu…

uniapp小程序九宫格抽奖

定义好奖品下标&#xff0c;计时器开始抽奖&#xff0c;请求接口&#xff0c;出现中奖奖品之后&#xff0c;获取中奖商品对应的奖品下标&#xff0c;再次计时器判断当前移动的小标是否为中奖商品的下标&#xff0c;并且是否转到3圈&#xff08;防止转1圈就停止&#xff09;&…

生成式人工智能:网络攻击者手中的破坏性力量

2022 年底&#xff0c;公开可用的生成式人工智能工具的推出使我们进入了人类历史上最大的技术革命之一。 一些人声称它的影响与互联网、手机、智能手机和社交媒体的引入一样大&#xff0c;甚至更大。这些新的生成式人工智能技术的采用和发展速度是我们以前从未见过的。 虽然这…

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

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

VX-3R APRS发射试验

VX-3R本身是不带APRS功能的&#xff0c;不过可能通过外加TNC实现APRS功能。 有大佬已经用Arduino实现了相应的发射功能&#xff1a; https://github.com/handiko/Arduino-APRS 我要做的&#xff0c;就是简单修改一下代码&#xff0c;做一个转接板。 YEASU官方没有给出VX-3R的音…