ReentrantReadWriteLock(可重入读写锁)源码解读与使用

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java源码解读-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

目录

1. 前言 

2. 读写锁是什么

3. ReentrantReadWriteLock是什么

4. 源码解读

4.1. ReadLock 

4.2. WriteLock

5. 基本使用

6.性能测试

7. 总结


1. 前言 

最近还在持续阅读JUC包下各种类的源码,JUC包下的每个类设计都十分巧妙,推荐小伙伴们去阅读下,一定会有不少收获的。

假如有人问你用过哪些读写锁,你会怎么回答呢,ReentrantLock和synchronized?实际上ReentrantLock和synchronized是互斥锁而不是读写锁,主要是为了确保对共享资源的互斥访问。

如果对读写锁部署,以下是一个参考回答:
在我的项目中,我主要使用 ReentrantLock 来确保对共享资源的互斥访问。虽然我没有直接使用过 ReentrantReadWriteLock,但我了解到它是 Java 提供的一个高级同步机制,特别适用于读多写少的场景。它维护了一对锁,一个用于读操作,允许多个线程同时读取资源,另一个用于写操作,确保在写入时独占访问。如果在未来遇到适合的场景,我会考虑使用 ReentrantReadWriteLock 来提高系统的并发性能

Java提供了两个读写锁类,分别是ReentrantReadWriteLock和StampedLock,ReentrantReadWriteLock就是我们今天要注重讲解的内容,StampedLock下次再更(鼠鼠肝不动了)。

2. 读写锁是什么

我们以往的学习旅程中,我们已经接触了如 synchronized 和 ReentrantLock 这样的互斥锁。这类锁的主要优势在于它们确保了线程的安全性,但它们的局限性在于同一时间仅允许一个线程持有锁,这在一定程度上降低了处理效率。另一方面,我们之前探讨的 Semaphore 虽然允许多个线程同时获取许可,但在保障线程安全方面表现不足。我们寻求的是一种既高效又安全的同步机制。

在实际应用场景中,数据读取操作的频率往往远高于写入操作。因此,富有远见的开发者们设计了一种新型锁——读写锁。在这种锁的设定下,读取数据时采用共享模式,允许多个线程同时持有读锁;而在写入数据时,为了确保线程安全,则切换到独占模式,确保同一时刻只有一个线程能够持有写锁。这样的设计理念催生了读写锁,它旨在提高并发性能的同时,不牺牲安全性。

3. ReentrantReadWriteLock是什么

ReentrantReadWriteLock是ReadWriteLock 接口的默认实现类,从名字可以看得出它也是一种具有可重入性的锁,同时也支持公平与非公平的配置,底层有两把锁,一把是 WriteLock (写锁),一把是 ReadLock(读锁) 。读锁是共享锁,写锁是独占锁。读锁可以被同时读,可以同时被多个线程持有,而写锁最多只能同时被一个线程持有,也是基于AQS实现的底层锁获取与释放逻辑。

ReentrantReadWriteLock类内部的组成架构图如下:

4. 源码解读

我首先抽取了ReentrantReadWriteLock类中的核心源码,如下:

// 内部结构
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/*1、用以继承AQS,获得AOS的特性,以及AQS的钩子函数*/
abstract static class Sync extends AbstractQueuedSynchronizer {// 具体实现
}
/*非公平模式,默认为这种模式*/
static final class NonfairSync extends Sync {// 具体实现
}
/*公平模式,通过构造方法参数设置*/
static final class FairSync extends Sync {// 具体实现
}
/*读锁,底层是共享锁*/
public static class ReadLock implements Lock, java.io.Serializable {private final Sync sync;protected ReadLock(ReentrantReadWriteLock lock) {sync = lock.sync;}// 具体实现
}
/*写锁,底层是独占锁*/
public static class WriteLock implements Lock, java.io.Serializable {private final Sync sync;protected WriteLock(ReentrantReadWriteLock lock) {sync = lock.sync;}// 具体实现
}// 构造方法,初始化两个锁
public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);
}// 获取读锁和写锁的方法
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

上面为底层的主要构造内容,ReentrantReadWriteLock中共写了5个静态内部类,各有功效,在上面的注释中也有提及。 

其中Sync,FairSync,NonFairSync在我们前面的文章时经常涉及到,大概都是Sync继承AQS,获得AQS的特性,然后实现AQS的钩子函数来自定义获取锁和释放锁的逻辑。FairSync和NonFairSync就是在Sync基础上加入了公平和非公平的特性。这三个类我们就不细讲了,我们着重看 ReadLock 和 WriteLock ,也就是读锁和写锁。

4.1. ReadLock 

public static class ReadLock implements Lock, java.io.Serializable {private static final long serialVersionUID = -5992448646407690164L;private final Sync sync;protected ReadLock(ReentrantReadWriteLock lock) {sync = lock.sync;}public void lock() {sync.acquireShared(1);}public void lockInterruptibly() throws InterruptedException {sync.acquireSharedInterruptibly(1);}public boolean tryLock() {return sync.tryReadLock();}public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}public void unlock() {sync.releaseShared(1);}}

ReadLock借助Sync来实现锁的获取与释放,可以通过构造函数传参来判断使用FairSync还是NonFairSync。

lock方法通过acquireShared共享方式来获取资源,深入acquireShared方法,发现里面调用了AQS的钩子函数acquireShared()

    public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);}

钩子函数acquireShared() 的实现在Sync,如下:

abstract static class Sync extends AbstractQueuedSynchronizer {protected final int tryAcquireShared(int unused) {// 1.获取当前线程,当前锁的状态(state值,0即为没人持锁)Thread current = Thread.currentThread();int c = getState();// 2.如果锁被占了(state!=0),且持有锁的线程不是当前线程,返回-1,获取锁失败if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;// 3.获取共享锁持有数量int r = sharedCount(c);// 4.调用readerShouldBlock()判断是否要排队(如果非公平就返回false)if (!readerShouldBlock() &&r < MAX_COUNT &&// 5.获取锁(CAS操作修改state值)compareAndSetState(c, c + SHARED_UNIT)) {// 如果读锁计数从0变为1,记录当前线程为第一个读线程,并设置其持有计数为1。if (r == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {// 如果当前线程已经是第一个读线程,增加其持有计数。firstReaderHoldCount++;} else {// 如果当前线程不是第一个读线程,则更新持有计数。HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}// 5.上述步骤中的任何条件都失败了,就进行完整的尝试获取读锁的循环,包括处理重入获取的情况。return fullTryAcquireShared(current);}
}
  1. 检查是否有写锁被其他线程持有

    • 使用getState()获取当前锁的状态。
    • 调用exclusiveCount(c)检查是否有独占锁(写锁)被持有(即状态字段的低16位是否为0)。
    • 如果有独占锁且持有独占锁的线程不是当前线程,则返回-1,表示获取读锁失败。
  2. 检查是否应该阻塞

    • 调用readerShouldBlock()方法来确定当前线程是否应该因为锁的队列策略而阻塞。这通常是基于公平性策略来决定的,如果是非公平模式,通常返回false。
    • 检查当前读锁的计数r是否小于最大值MAX_COUNT。
  3. 尝试更新状态

    • 使用compareAndSetState(c, c + SHARED_UNIT)尝试通过CAS操作增加读锁的计数(状态字段的高16位)。如果成功,则表示获取读锁成功。
    • 如果读锁计数从0变为1,记录当前线程为第一个读线程,并设置其持有计数为1。
    • 如果当前线程已经是第一个读线程,增加其持有计数。
    • 如果当前线程不是第一个读线程,则更新cachedHoldCounter或readHolds中的持有计数。
  4. 如果尝试失败

    • 如果上述步骤中的任何条件失败(如应该阻塞、CAS操作失败、读锁计数饱和等),则调用fullTryAcquireShared(current)方法,这个方法会进行完整的尝试获取读锁的循环,包括处理重入获取的情况。

总之,tryAcquireShared方法是一个尝试快速获取读锁的方法,它会尽可能地避免阻塞,并在可能的情况下立即返回。如果快速路径失败,它会调用fullTryAcquireShared方法进行更全面的尝试。

4.2. WriteLock

public static class WriteLock implements Lock, java.io.Serializable {private static final long serialVersionUID = -4992448646407690164L;private final Sync sync;protected WriteLock(ReentrantReadWriteLock lock) {sync = lock.sync;}public void lock() {sync.acquire(1);}public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}public boolean tryLock( ) {return sync.tryWriteLock();}public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));}public void unlock() {sync.release(1);}public Condition newCondition() {return sync.newCondition();}public String toString() {Thread o = sync.getOwner();return super.toString() + ((o == null) ?"[Unlocked]" :"[Locked by thread " + o.getName() + "]");}public boolean isHeldByCurrentThread() {return sync.isHeldExclusively();}public int getHoldCount() {return sync.getWriteHoldCount();}}

lock方法通过acquire独占方式来获取资源,深入acquire方法,里面调用了AQS的钩子函数tryAcquire()

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

同上,钩子函数acquireShared() 的实现在Sync,如下: 

abstract static class Sync extends AbstractQueuedSynchronizer {protected final boolean tryAcquire(int acquires) {// 1.获取当前线程和锁的状态Thread current = Thread.currentThread();int c = getState();int w = exclusiveCount(c);// 2.如果锁被持有(state != 0)if (c != 0) {// 3.如果写锁计数w为0,则表示读锁被持有,// 此时获取写锁失败,返回false。if (w == 0 || current != getExclusiveOwnerThread())return false;// 4.重入次数加上acquires参数后超过了最大计数MAX_COUNT,则抛出错误if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");// 5.当前线程持有锁,state+1即可,保持锁的重入性setState(c + acquires);return true;}// 6.如果writerShouldBlock()方法返回true,表示当前线程应该因为锁的队列策略而阻塞,则返回false。否则通过CAS操作获取锁if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;setExclusiveOwnerThread(current);return true;}
}
  1. 检查锁的状态

    • 使用getState()获取当前锁的状态。
    • 使用exclusiveCount(c)获取状态字段的低16位,即写锁的计数。
    • 如果锁的状态c不为0,则表示锁已经被持有。
  2. 处理锁已被持有的情况

    • 如果写锁计数w为0,则表示读锁被持有,或者写锁被其他线程持有,此时获取写锁失败,返回false。
    • 如果当前线程不是持有写锁的线程,则获取写锁失败,返回false。
    • 如果当前线程已经持有写锁,并且重入次数加上acquires参数后超过了最大计数MAX_COUNT,则抛出错误,因为超过了锁的最大重入次数。
    • 如果当前线程已经持有写锁,则增加写锁的计数(重入锁),更新状态,并返回true。
  3. 尝试获取锁

    • 如果writerShouldBlock()方法返回true,表示当前线程应该因为锁的队列策略而阻塞,则返回false。
    • 使用compareAndSetState(c, c + acquires)尝试通过CAS操作更新状态来获取写锁。如果成功,则表示获取写锁成功。
    • 如果CAS操作成功,设置当前线程为锁的持有者setExclusiveOwnerThread(current),并返回true。

5. 基本使用

那么这个读写锁如何使用呢?我们接下来通过一个小小的案例来示范下。

public class Test {private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private int data = 0;/*** 写方法* @param value*/public void write(int value) {//注意,获取锁的操作要在try/finally外面lock.writeLock().lock(); // 获取写锁try {data = value;System.out.println("线程:"+Thread.currentThread().getName() + "写" + data);} finally {lock.writeLock().unlock(); // 释放写锁}}public void read() {lock.readLock().lock(); // 获取读锁try {System.out.println("线程:" + Thread.currentThread().getName() + "读" + data);} finally {lock.readLock().unlock(); // 释放读锁}}public static void main(String[] args) {Test test = new Test();// 创建读线程Thread readThread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {test.read();}});Thread readThread2 = new Thread(() -> {for (int i = 0; i < 5; i++) {test.read();}});// 创建写线程Thread writeThread = new Thread(() -> {for (int i = 0; i < 5; i++) {test.write(i);}});readThread1.start();readThread2.start();writeThread.start();try {readThread1.join();readThread2.join();writeThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}

输出结果为:

线程:Thread-0读0
线程:Thread-1读0
线程:Thread-2写0
线程:Thread-2写1
线程:Thread-2写2
线程:Thread-2写3
线程:Thread-2写4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4
线程:Thread-1读4

 通过输出内容,我们进一步得证,在ReentrantReadWriteLock在使用读锁时,可以支持多个线程获取读资源,而在调用写锁时,其他读线程和写线程均阻塞等待当前线程写完。

6.性能测试

既然都说读写锁能够提高并发性能,接下来我们就测试以下,测试代码已同步到仓库:Concurrent-MulThread/7-lock-performance-test(github.com)

public class LockPerformanceTest {private static final int READ_THREADS = 10; //读操作线程数private static final int WRITE_THREADS = 2; //写操作线程数private static final int ITERATIONS = 100000; // 操作次数private static final Lock reentrantLock = new ReentrantLock();private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();private static final Lock readLock = readWriteLock.readLock();private static final Lock writeLock = readWriteLock.writeLock();private static int sharedResource = 0;public static void main(String[] args) throws InterruptedException {long startTime, endTime;// 测试 ReentrantLockstartTime = System.currentTimeMillis();testReentrantLock();endTime = System.currentTimeMillis();System.out.println("ReentrantLock time: " + (endTime - startTime) + " ms");// 重置共享资源sharedResource = 0;// 测试 ReentrantReadWriteLockstartTime = System.currentTimeMillis();testReentrantReadWriteLock();endTime = System.currentTimeMillis();System.out.println("ReentrantReadWriteLock time: " + (endTime - startTime) + " ms");}private static void testReentrantLock() throws InterruptedException {CyclicBarrier barrier = new CyclicBarrier(READ_THREADS + WRITE_THREADS);for (int i = 0; i < READ_THREADS; i++) {new Thread(() -> {try {barrier.await();for (int j = 0; j < ITERATIONS; j++) {reentrantLock.lock();// 读取共享资源int value = sharedResource;reentrantLock.unlock();}} catch (Exception e) {e.printStackTrace();}}).start();}for (int i = 0; i < WRITE_THREADS; i++) {new Thread(() -> {try {barrier.await();for (int j = 0; j < ITERATIONS; j++) {reentrantLock.lock();// 写入共享资源sharedResource++;reentrantLock.unlock();}} catch (Exception e) {e.printStackTrace();}}).start();}}private static void testReentrantReadWriteLock() throws InterruptedException {CyclicBarrier barrier = new CyclicBarrier(READ_THREADS + WRITE_THREADS);for (int i = 0; i < READ_THREADS; i++) {new Thread(() -> {try {barrier.await();for (int j = 0; j < ITERATIONS; j++) {readLock.lock();// 读取共享资源int value = sharedResource;readLock.unlock();}} catch (Exception e) {e.printStackTrace();}}).start();}for (int i = 0; i < WRITE_THREADS; i++) {new Thread(() -> {try {barrier.await();for (int j = 0; j < ITERATIONS; j++) {writeLock.lock();// 写入共享资源sharedResource++;writeLock.unlock();}} catch (Exception e) {e.printStackTrace();}}).start();}}
}

 输出结果为:

ReentrantLock time: 31 ms
ReentrantReadWriteLock time: 1 ms

可以看到在都保证了线程安全的情况下,ReentrantReadWriteLock比ReentrantLock快了不少,ReentrantReadWriteLock性能这么快,那么有啥缺点呢?答案是有的:

  • 不支持分布式:单机锁的通病,这个没办法
  • 线程饥饿问题:在写的时候,是独占模式,其他线程不能读也不能写,这时候若有大量的读操作的话,那这些线程也只能等待着,从而带来写饥饿。

在另一个读写锁工具类StampedLock中就解决了饥饿问题,下次再讲解

7. 总结

ReentrantReadWriteLock是 Java 提供的一个高级同步机制,特别适用于读多写少的场景。它维护了一对锁,一个用于读操作,允许多个线程同时读取资源,另一个用于写操作,确保在写入时独占访问。相比于ReentrantLock 直接锁读写会有更细的锁粒度,提高读写的并发性能,但也存在线程饥饿问题,也就是在写的时候,其他线程不能读也不能写,这时候若有大量的读操作的话,就会让很多线程等待,造成饥饿问题,在StampedLock中解决了这个问题,下次讲解。

此外,博主祝您五一小长假快乐~~

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

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

相关文章

ColdDTA:利用数据增强和基于注意力的特征融合进行药物靶标结合亲和力预测

ColdDTA发表在Computers in Biology and Medicine 的一篇一区文章 突出 • 数据增强和基于注意力的特征融合用于药物靶点结合亲和力预测。 • 与其他方法相比&#xff0c;它在 Davis、KIBA 和 BindingDB 数据集上显示出竞争性能。 • 可视化模型权重可以获得可解释的见解。 …

Python梯度提升决策树库之lightgbm使用详解

概要 LightGBM是一个快速、分布式、高性能的梯度提升决策树(Gradient Boosting Decision Tree)库,它在机器学习和数据挖掘领域被广泛应用。本文将介绍LightGBM库的安装方法、主要特性、基本功能、高级功能、以及在实际应用中的场景和总结。 安装 首先,需要安装LightGBM库…

第Y9周:重要模块解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 目录 以con.py为例&#xff1a; 一、autopad 二、Conv 三、Focus 四、C2f 文件…

Golang | Leetcode Golang题解之第66题加一

题目&#xff1a; 题解&#xff1a; func plusOne(digits []int) []int {n : len(digits)for i : n - 1; i > 0; i-- {if digits[i] ! 9 {digits[i]for j : i 1; j < n; j {digits[j] 0}return digits}}// digits 中所有的元素均为 9digits make([]int, n1)digits[0]…

如何基于nginx组建多个子目录网站

华子目录 实验要求实验步骤 实验要求 组建多个子目录网站www.openlab.com&#xff0c;该网站有2个子目录www.openlab.com/sxhkt和www.openlab.com/zywww.openlab.com/sxhkt使用http读取www.openlab.com/zy使用https读取 实验步骤 准备工作 [rootserver ~]# setenforce 0[ro…

串口单线半双工转换电路

用来把单线半双工模式的串口转换成双线&#xff0c;然后才能连接到普通的双线USB 串口模块&#xff0c;比如CH340 之类的。电路设计来自大佬的博客&#xff1a;AVR half-duplex software UART supporting single pin operation。他在Arduino 上用软件模拟串口功能&#xff0c;利…

测试PG中事务隔离级别

我们知道事务隔离级别有&#xff1a;读未提交&#xff0c;读已提交&#xff0c;可重复读&#xff0c;可序列化。 读未提交 存在 脏读&#xff0c;不可重复读&#xff0c;幻读&#xff1b; 读已提交 存在 不可重复读&#xff0c;幻读 可重复读 存在 幻读 PG 下默认为读已提交…

微信小程序使用蓝牙连接硬件

目录 一、蓝牙官方api文档 二、蓝牙重要参数介绍 三、案例教程 1. 获取蓝牙权限&#xff08;openBluetoothAdapter&#xff09; 2. 开始搜索蓝牙设备(startBluetoothDevicesDiscovery) 3. 监听搜索到新设备的事件(onBluetoothDeviceFound) 4.连接蓝牙设备&#xff08;crea…

SpringData JPA - ORM 框架下,打造高效数据访问层

目录 一、SpringData JPA 概述 1.1、什么是 JPA 1.2、什么是 ORM 1.3、什么是 Hibernate 1.4、JPA 和 Hibernate 的关系 1.5、JPA 的优势 二、SpringData JPA 实战开发 2.1、依赖 2.2、配置文件 2.3、启动类 2.4、创建实体 2.5、基于 JpaRepository 的 CRUD 三、…

网络安全审计

一、什么叫网络安全审计 网络安全审计是按照一定的安全策略&#xff0c;利用记录、系统活动和用户活动等信息&#xff0c;检查、审查和检验操作时间的环境及活动&#xff0c;从而发现系统漏洞、入侵行为或改善系统性能的过程&#xff0c;它是提高系统安全性的重要手段。 系统…

数据结构:时间复杂度/空间复杂度

目录 一、时间复杂度 定义 常见的时间复杂度 如何计算时间复杂度 计算方法 三、实例分析 二、空间复杂度 定义 重要性 常见的空间复杂度 二、空间复杂度 定义 重要性 常见的空间复杂度 计算方法 三、实例分析 大O的渐进表示法 最好情况&#xff08;Best Case…

spring框架学习记录(1)

前半个月一直在应付期中考试&#xff0c;快被折磨似了orz 文章目录 SpringIoC(Inversion of Control) 控制反转与DI(Dependency Injection)依赖注入bean相关bean配置bean实例化bean的生命周期 依赖注入相关依赖注入方式依赖自动装配 容器创建容器获取bean Spring IoC(Inversi…

leetcode295. 数据流的中位数

class MedianFinder {//A为小根堆&#xff0c;B为大根堆List<Integer> A,B;public MedianFinder() {A new ArrayList<Integer>();B new ArrayList<Integer>();}public void addNum(int num) {int m A.size(),n B.size();if(m n){insert(B,num);int top …

BeanFactory 源码浅析

BeanFactory 功能介绍 BeanFactory 是核心容器&#xff0c;负责管理 Bean 对象 BeanFactory 接口的功能只有一个 getBean() 方法BeanFactory 的实现类&#xff08;DefaultListableBeanFactory&#xff09;包含&#xff1a;控制反转、基本的依赖注入、Bean 生命周期的各种功能…

从浏览器输入url到页面加载(八)你的web网站有几台服务器?

你有没有想过一个问题&#xff0c;做为一名前端开发&#xff0c;你的网站上线后&#xff0c;准备了几台服务器&#xff1f;前端静态资源用了几台&#xff0c;你调接口的那个后端部署了几台&#xff1f; 目录 1 没接触过这个问题很正常 2 当访问量上升的时候 2.1 提升带宽 …

绝了!这是我见过最详细的HashMap源码解析

1 概述 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只适用于单线程环境,多线程环境可以采用并发包下的concurrentHashMap HashMap 实现了Serializable接口&#x…

ArmSoM-Sige5 RK3576开发板 正式发布!

简介​ ArmSoM-Sige5 采用Rockchip RK3576第二代8nm高性能AIOT平台&#xff0c;6 TOPS算力NPU&#xff0c;最大可配16GB大内存。支持8K视频编解码&#xff0c;拥有丰富的接口&#xff0c;支持双千兆网口&#xff0c;WiFi6 & BT5和多种视频输出。支持多种操作系统&#xff…

HTML5实用大全(Part.2)

引言&#xff1a; 哈喽&#xff0c;各位小伙伴们大家好呀&#xff0c;学习了上一篇关于HTML5的文章后&#xff0c;你是否对于入门HTML5有了一定的基础了呢&#xff0c;本篇博客我们将继续学习HTML5的不同标签&#xff0c;跟上队伍&#xff0c;准备出发咯&#xff01; 1.标签之…

2024五一杯数学建模竞赛A题完整成品论文和代码分析:建立钢板切割的工艺路径动态规划、贪心与分层优化模型

2024五一杯数学建模竞赛A题&#xff1a;建立钢板切割的工艺路径动态规划、贪心与分层优化模型 2024五一数学建模A题完整代码和成品论文获取↓↓↓↓↓ https://www.yuque.com/u42168770/qv6z0d/gyoz9ou5upvkv6nx?singleDoc# 本文文章较长&#xff0c;建议先目录。经过不懈的…

三星一季度利润飙涨932%!AI引爆存储热,未来研发狠砸AI

⏩三星一季度利润飙涨932%&#xff01;AI引爆存储热&#xff0c;未来研发狠砸AI 三星电子公布了第一季度财报数据&#xff0c;显示其利润飙涨932.8%。得益于AI拉动的广泛支出&#xff0c;三星电子一季度利润激增。三星表示&#xff0c;预计第二季度业务将主要由生成式人工智能…