【JavaEE】线程安全的集合类

server/2024/10/20 10:11:39/

目录

前言

多线程环境使用ArrayList

多线程环境使用队列

多线程环境下使用哈希表

1.HashTable

2.ConcurrentHashMap

面试题

1.ConcurrentHashMap的读会否需要加锁,为什么?

2.介绍下ConcurrentHashMap的锁分段技术?

3.ConcurrentHashMap在jdk1.8做了哪些优化?

4.HashTable和HashMap、ConcurrentHashMap之间的区别?


前言

在前面我们学习了一些在中java集合类,例如ArrayList、Queue、HashMap、StringBuilder等一些常见的集合类,但这些都是线程不安全的类,不能在多线程中使用。考虑多线程的情况下,我需要使用一些线程安全的类,Vector、Stack、HashTable是线程安全的类,但并不建议使用。

在多线程的情况下,建议自己加锁,或者使用一些带锁的数据结构

多线程环境使用ArrayList

ArrayList本身是一个线程不安全的集合类,在多线程的情况下,对于其读和写操作,存在着线程安全问题,因此,提出了下面几种解决方法:

  1. 自己使用同步机制(synchronized或者ReentrantLock)
  2. Collection.synchronizedList(new ArrayList);其实就是在相关方法面前进行synchronized的加锁。

synchronizedList 是标准库提供的⼀个基于synchronized进⾏线程同步的List.synchronizedList 的关键操作上都带有synchronized

  3.CopyOnWriteArrayList:即写时复刻的容器

在进行读操作时,容器不用做任何改变。当我们往容器里添加元素的时候,不会直接往当前容器里添加,而是会先对当前容器Copy复制出一个新的容器,往后往新的容器里添加元素。当添加完元素之后,再将原容器的引用指向新的容器。

我们可以查看CopyOnWriteArrayList中的add方法,我们可以看到和我们上面说的一致。

当我们想要进行写操作,会先进行拷贝,再在拷贝的数组里存放数据。

CopyOnWriteArrayList容器的优缺点

优点

  1. 读操作无需加锁:由于使用了写时拷贝策略,在读取操作的时候,可以在没有锁的情况下进行,提高了读取操作的性能。在读多写少的场景下,不需要加锁竞争。
  2. 线程安全CopyOnWriteArrayList是一个线程安全的类,在多线程环境下使用无需加锁。

缺点

  1. 内存占用大CopyOnWriteArrayList是写时复制,在进行写操作的时候,每次都是复制整个底层数组,如果数组比较大,就会导致有明显的性能开销。所以CopyOnWriteArrayList并不适合频繁写的场景。
  2. 数据一致性CopyOnWriteArrayList只能保证数据的最终一致性,不能保证数据的实时一致性。如果想要马上获取到写入的数据,是不能的。(写操作没有完成时,此时获取到的数据依旧是旧数组的数据)。

CopyOnWriteArrayList适用于读多写少且数据量适中的情况下,如果读少写多且数据量较大,不适合使用,内存开销大,可能会有性能问题。

多线程环境使用队列

队列是一种“先进先出”的数据结构,线程安全问题主要在当队列为空时读取或者满时插入的情况下。为了解决这种问题,当队列为空时读取数据或者队列为满时想插入数据时需要让执行操作的线程进入阻塞等待。在java.util.concurrent中,给我们提供了以下几种队列:

  1. ArrayBlockingQueue:基于数组实现的阻塞队列;
  2. LinkedBlockingQueue:基于链表实现的阻塞队列;
  3. PriorityBlockingQueue:基于堆实现的带优先级的阻塞队列;
  4. TransferQueue:最多只包含一个元素的阻塞队列。

多线程环境下使用哈希表

HashMap是一个线程不安全的集合类。在多线程下哈希表可以使用:

  1. HashTable
  2. ConcurrentHashMap

1.HashTable

HashTable只是给关键方法加上了synchronized关键字。这相当于直接给HashTable对象本身加锁。

  1. 如果多线程访问同一个HashTable就会直接造成锁冲突。
  2. size属性也是通过synchronized来控制同步,也是比较慢的。
  3. 一旦触发扩容,就有该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝,效率非常低。 

 

一个HashTable只有一把锁,当有两个线程访问HashTable中的任意数据就会出现锁竞争。

2.ConcurrentHashMap

针对上面这种情况,ConcurrentHashMap做出来一系列的优化。

1.ConcurrentHashMap是给每个hash表中的“链表”进行加锁(分段锁),将HashTable的一个大锁转换成一个个加在哈希桶的小锁,大大降低了锁冲突。只要有两个线程访问的恰好是同一个哈希桶上的数据才会出现锁冲突

2.引入了CAS这样的原子操作,像修改size这样的操作,不会进行加锁,直接借助CAS完成即可。

3.ConcurrentHashMap对读操作没有进行加锁(使用了volatile保证从内存读取结果,确保读操作,不会读到“修改一半的数据”),只针对写操作进行加锁。 

4.优化了Hash表的扩容方式:对于普通hash扩容,需要创建一个新的Hash表,再把数据搬运过去,这一系列操作,可能就在一次put操作内完成,这会导致put开销非常大,且耗时非常长。ConcurrentHashMap则是选择“化整为零”不会在一次操作中把所有的数据都搬运过去,而是每次搬运一部分。

  • 发现需要扩容的线程,只需要创建一个新的数组,同时只搬几个元素过去。
  • 扩容期间,新老数组同时存在。
  • 后序每个来操作ConcurrentHashMap的线程,都会参与搬家的过程,每个操作负责搬运一小部分元素。
  • 搬完最后一个元素再把老数组删掉。
  • 这个期间,插入只往新数组加。
  • 这个期间,查找需要同时查新数组和老数组。

面试题

1.ConcurrentHashMap的读会否需要加锁,为什么?

读操作没有加锁,目的是为了进一步降低锁冲突的概率,为了保证读到刚修改的数据,搭配了volatile关键字。

2.介绍下ConcurrentHashMap的锁分段技术?

这个是java1.7中采用的技术,java1.8已经不再使用了,简单的来说,就是把若干个哈希桶分成一个“段”(Segment),针对每个段进行加锁。

目的也是为了降低锁竞争的概率,当两个线程访问的数据恰好在同一个段时,才会触发锁竞争。

3.ConcurrentHashMap在jdk1.8做了哪些优化?

取消了锁分段,直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对象)。

将原来数组+链表的实现方式改进成数组+链表/红黑树的方式。当链表较长的时候(大于等于8个元素)就转换成红黑树。

4.HashTable和HashMap、ConcurrentHashMap之间的区别?

HashMap:线程不安全,key允许为null

HashTable:线程安全,使用synchronized锁Hashtable对象,效率较低,key不允许为null。

ConcurrentHashMap:线程安全,使用synchronized锁每个链表头结点,锁冲突概率低,充分利用CAS机制,优化了扩容方式,key不允许为null。


以上就是本篇所有内容,若有不足,欢迎指正~


http://www.ppmy.cn/server/99465.html

相关文章

宠物空气净化器什么牌子好?希喂、美的测评推荐

家里养了两只猫,每天晚上和我入眠,早上睡醒过来就看到猫睡在我身边,这一刻幸福感爆棚。幸福感爆棚的同时,无力感也袭来。主要是因为虽然每天玩得都很开心,但是家里的变化让我不禁感慨这是真实存在的吗。一回到家就会发…

SpringBoot中整合Mybatis

一、Mybatis快速入门 1.1、在相应的模块中添加依赖的坐标 首先创建一个maven项目 在对应的pom.xml文件中引入下面的依赖 <dependencies><!--mybatis 依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artif…

笔记工具的选择:构建编程学习的高效系统

在编程学习的海洋中&#xff0c;高效的笔记记录和整理方法就像一张珍贵的航海图&#xff0c;能够帮助我们在浩瀚的知识中找到方向。如何建立一个既能快速记录又易于回顾的笔记系统&#xff1f;如何在繁忙的学习中保持笔记的条理性&#xff1f;让我们一起探讨如何打造属于自己的…

uniapp vue 在适配百度小程序平台动态:style

uniapp vue 在适配百度小程序平台动态:style踩坑报错Unexpected string concatenation of literals 抖快平台动态style写法基本是 <view :style"{width: 686rpx, height: (setHeight 96) rpx}"> </view>这种写法在百度上会又解析报错&#xff1a; Une…

Postman Pre-request Script

这个其实是普通的js脚本&#xff0c;有一些和postman的通信他也提供了一些快捷命令如下 postman常用参数使用 环境变量 //设置当前环境变量 pm.environment.set("key", "value"); //获取当前环境变量 pm.environment.get("key"); //清除当前…

360安全大模型为什么是“非卖品”?

大模型虽然不是万能的&#xff0c;但是没有大模型又是万万不能的。以AI大模型为动力引擎&#xff0c;AI正在重塑各行各业&#xff0c;并快速“飞入寻常百姓家”。 AI安全 以“模”制“模” 2024年全国两会&#xff0c;“人工智能”首次被写入政府工作报告。报告中提出&#xff…

一文搞懂后端面试之不停机数据迁移【中间件 | 数据库 | MySQL | 数据一致性】

数据迁移方面的工作&#xff1a; 重构老系统&#xff1a;使用新的表结构来存储数据单库拆分分库分表、分库分表扩容大表修改表结构定义 数据备份工具 MySQL上常用的两款数据备份工具&#xff1a;mysqldump和XtraBackup mysqldump&#xff1a;一个用于备份和恢复数据库的命令…

缓存淘汰策略有哪些?

缓存淘汰策略是在缓存空间有限时&#xff0c;用于决定哪些数据应该从缓存中移除的方法。常见的缓存淘汰策略包括以下几种&#xff1a; 最近最少使用&#xff08;Least Recently Used, LRU&#xff09;策略&#xff1a; 核心思想&#xff1a;将最近最少使用的数据淘汰&#xff0…