首页 > 编程语言 >【JavaEE】线程安全的集合类

【JavaEE】线程安全的集合类

时间:2024-08-18 14:22:53浏览次数:12  
标签:ConcurrentHashMap 加锁 JavaEE 线程 HashTable 集合 操作 多线程

目录

前言

多线程环境使用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。


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

标签:ConcurrentHashMap,加锁,JavaEE,线程,HashTable,集合,操作,多线程
From: https://blog.csdn.net/zhyhgx/article/details/141131884

相关文章

  • 集合
    集合集合和数组既然都是容器,它们有啥区别呢?数组的长度是固定的。集合的长度是可变的。数组中可以存储基本数据类型值,也可以存储对象,而集合中只能存储对象集合有更加丰富的API对数据进行处理集合主要分为两大系列:Collection和Map,Collection表示一组对象,Map表示一组映射关系......
  • Thread-多线程
    多线程并发与并行(了解)并行(parallel):指多个事件任务在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个处理器上同时执行。单核CPU同一时刻只能处理一条指令,所以单核CPU做不到并行处理。并发(concurrency):指两个或多个事件在同一个微小的时间段内发生。指在同一个时刻只能有一......
  • JAVA13-线程
    1.线程的简介1.1什么是进程        进程,是正在运行的程序实例,是操作系统进行资源分配的最小单位。每个进程都有它自己的地址空间和系统资源(比如CPU时间,内存空间,磁盘IO等)。多个进程可以同时执行,每个进程在运行时都不会影响其他进程的运行,资源不共享。程序是一个没......
  • Java集合相关面试题(超详细)附:Java详细面试资料直链
    Java集合相关面试题内容搬运资料,来源见文章末尾仅为分享,不涉及任何获利行为,~(~ ̄▽ ̄)~可别发律师函啊链接:面试资料提取码:s4w8ArrayList底层实现是数组LinkedList底层实现是双向链表HashMap的底层实现使用了众多数据结构,包含了数组、链表、散列表、红黑树等1算法复杂度分......
  • 常见Java集合
    1.fail-fast和fail-safe机制fail-fast(快速失败)和fail-safe(安全失败)是两种在遍历集合时处理并发修改的策略。1.1.fail-fast机制遍历集合时,如果发现集合被修改(除了通过迭代器自身的remove方法),会立即抛出ConcurrentModificationException异常。这种机制的目的是快速检测......
  • Java 线程的六种状态及转化
    原文:Java:线程的六种状态及转化java.lang.Thread.State枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()方法获取当前线程的状态。线程状态解释NEW尚未启动的线程状态,即线程创建,还未调用start方法RUNNABLE就绪状态(调用start,等待调度)+正在运行......
  • .NET中各种线程同步锁
    编程编的久了,总会遇到多线程的情况,有些时候我们要几个线程合作完成某些功能,这时候可以定义一个全局对象,各个线程根据这个对象的状态来协同工作,这就是基本的线程同步。​支持多线程编程的语言一般都内置了一些类型和方法用于创建上述所说的全局对象也就是锁对象,它们的作用类似,使用......
  • delphi多线程文件复制怎么实现
    在Delphi中,可以使用TThread类来实现多线程文件复制。以下是一个示例代码:unitUnit1;interfaceusesWinapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,System.Classes,Vcl.Graphics,Vcl.Controls,Vcl.Forms,Vcl.Dialogs,Vcl.StdCtrls;type......
  • 这是我见过的(最全面,最优质的)Java的List集合常见面试题汇总,一文讲完,通俗易懂,看完不吊打
    Arraylist和数组(Array)的区别?ArrayList内部基于动态数组实现,比Array(静态数组)使用起来更加灵活:ArrayList会根据实际存储的元素动态地扩容或缩容,而Array被创建之后就不能改变它的长度了。ArrayList允许你使用泛型来确保类型安全,Array则不可以。ArrayList中只能存储对象......
  • 集合IO流
    packagecom.shujia.day17.ketang;/*键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低存入文本文件*/importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.FileWriter;importjava.io.IOException;import......