JUC并发集合

news2025/5/20 9:07:37

目录

传统类集框架的弊端

1.并发集合的类型

2.并发单值集合

3.并发多值集合

4.跳表集合 


传统类集框架的弊端

传统的类集框架存在一个非常严重的弊端。那就是在多线程的情况下对集合修改会报错。

如下代码

package Example2123;

import java.util.ArrayList;
import java.util.List;

public class javaDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
//        多线程
        for (int i =0;i<10;i++){
//            多线程下多次放入数据与输出数据
            new Thread(()->{
                for (int j=0;j<10;j++){
                    list.add(Thread.currentThread().getName());
                    System.out.println(list);
                }
            },"Thread"+i).start();
        }
    }
}

 出现了修改异常,因为传统的类集框架是针对单线程设计的。如果出现多线程同时修改的情况下再输出则会出现异常。为此JUC中就提供了并发集合来解决集合安全的问题


1.并发集合的类型

No.集合接口集合类描述
1ListCopyOnWriteArrayList当于线程安全的 ArrayList,并支持高并发访问
2SetCopyOnWriteArraySet相当于线程安全的 HashSet,基于 CopyOnWriteArrayList 实现
3SetConcurrentSkipListSet相当于线程安全的 TreeSet,基于跳表结构实现,并支持高并发访问
4MapConcurrentHashMap相当于线程安全的 HashMap,支持高并发访问
5MapConcurrentSkipListMap相当于线程安全的 TreeMap,基于跳表结构实现,并支持高并发访问
6ArrayBlockingQueue基于数组实现的线程安全的有界阻塞队列
7QueueLinkedBlockingQueue单向链表实现的阻塞队列,支持 FIFO 处理
8QueueConcurrentLinkedQueue单向链表实现的无界队列,支持 FIFO 处理
9DequeLinkedBlockingDeque双向链表实现的双向并发阻塞队列,支持 FIFO、FILO 处理
10DequeConcurrentLinkedDeque双向链表实现的无界队列,支持 FIFO、FILO 处理

2.并发单值集合

并发单值在类集框架中分为两类,一类是List链表,一条是Set集合。以下分别是并发链表和并发集合

1.CopyOnWriteArraylist (并发链表)

常用方法如下:

方法名描述
boolean add(E e)将指定元素追加到此列表的末尾。
void add(int index, E element)将指定元素插入此列表中的指定位置。
boolean remove(Object o)从列表中移除指定元素的第一个匹配项(如果存在)。
E remove(int index)移除列表中指定位置的元素。
E get(int index)返回列表中指定位置的元素。
int size()返回列表中的元素数。
boolean contains(Object o)如果列表包含指定的元素,则返回 true
Iterator<E> iterator()返回在列表的元素上进行迭代的迭代器。

案例代码:

package Example2124;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class javaDemo {
    public static void main(String[] args) {
//        创建并发链表
        List<String> list = new CopyOnWriteArrayList<>();
//        创建多线程
        for (int i=0;i<10;i++){
            new Thread(()->{
//                多线程对并发集合进行修改
                for (int j=0;j<10;j++){
                    list.add(Thread.currentThread().getName());
                    System.out.println(list);
                }
            },"Thread"+i).start();
        }
    }
}

注意:

  1. 由于CopyOnWriteArrayList是基于数组实现的并发集合类。 在执行修改操作时候都会创建一个新的数组,再将更新后的数据复制进入新的数组中。所以该类实现修改的效率比较低。
  2. 在使用迭代器iterator的时候,并不支持iterato的remove删除元素的操作。

 2.CopyOnWriteArraySet

以下是常用的方法

方法名描述
boolean add(E e)将指定元素添加到此集合中,如果已存在则不添加。
boolean remove(Object o)从集合中移除指定元素的第一个匹配项(如果存在)。
boolean contains(Object o)如果集合包含指定的元素,则返回 true
int size()返回集合中的元素数。
boolean isEmpty()如果集合不包含任何元素,则返回 true
void clear()从集合中移除所有元素。
Iterator<E> iterator()返回在集合的元素上进行迭代的迭代器。

案例代码:

package Example2125;

import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class javaDemo {
    public static void main(String[] args) {
//        创建并发集合
        Set<String> set = new CopyOnWriteArraySet<>();
//        多个人同时写自己喜欢的Hobby
        String hobbies[] = new String[]{"篮球","逛街","打游戏","跑步","写编程"};
        for (int i=0;i<10;i++){
            new Thread(()->{
                for (int j=0;j< hobbies.length;j++){
                    set.add(hobbies[j]);
                }
            }).start();
        }
        System.out.println(set);
    }
}

注意:CopyOnWriteArraySet是依赖于CopyOnWriteArrayList进行数据保存的,所以对应的迭代器输出时候也不能出现删除操作


3.并发多值集合

1.ConcurrentHashMap

原理:将一个哈希表的结构分为多个片段,并且每一个片段中都有互斥锁,所以在同一个片段中线程之间是互斥的,而由于由多个片段,所以可以进行异步处理。这样保证了安全的前提下又能保证

常用的方法

方法名描述
V put(K key, V value)将指定的键值对添加到映射中。如果键已经存在,则替换对应的值,并返回旧值。
V get(Object key)返回指定键所映射的值,如果键不存在,则返回 null
boolean containsKey(Object key)如果映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value)如果映射将一个或多个键映射到指定值,则返回 true
V remove(Object key)从映射中移除指定键的映射关系,并返回该键对应的值。
boolean remove(Object key, Object value)仅当指定的键当前映射到指定的值时,才从映射中移除该键的映射关系。
int size()返回映射中键值对的数目。
boolean isEmpty()如果映射不包含任何键值对,则返回 true
void clear()从映射中移除所有的键值对。

案例代码:

package Example2126;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class javaDemo {
    public static void main(String[] args) {
//        创建并发哈希表
        Map<String,Integer> map = new ConcurrentHashMap<>();
        for (int i=0;i<5;i++){
            new Thread(()->{
//                多线程传输数据
                for (int j = 0;j<5;j++){
                    map.put(Thread.currentThread().getName(),j);
                }
            },"Thread"+i).start();
        }
//        输出map
        System.out.println(map);
    }
}

面试题 Java8开始ConcurrentHashMap,为什么舍弃分段锁?

ConcurrentHashMap的原理是引用了内部的 Segment ( ReentrantLock )  分段锁,保证在操作不同段 map 的时候, 可以并发执行, 操作同段 map 的时候,进行锁的竞争和等待。从而达到线程安全, 且效率大于 synchronized。

但是在 Java 8 之后, JDK 却弃用了这个策略,重新使用了 synchronized+CAS。

弃用原因

通过  JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:

  1. 加入多个分段锁浪费内存空间。
  2. 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
  3. 为了提高 GC 的效率
  4. 新的同步方案
  5. 既然弃用了分段锁, 那么一定由新的线程安全方案, 我们来看看源码是怎么解决线程安全的呢?(源码保留了segment 代码, 但并没有使用)。

 面试题 ConcurrentHashMap(JDK1.8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?

(1)锁的粒度

首先锁的粒度并没有变粗,甚至变得更细了。每当扩容一次,ConcurrentHashMap的并发度就扩大一倍。

(2)Hash冲突

JDK1.7中,ConcurrentHashMap从过二次hash的方式(Segment -> HashEntry)能够快速的找到查找的元素。在1.8中通过链表加红黑树的形式弥补了put、get时的性能差距。
JDK1.8中,在ConcurrentHashmap进行扩容时,其他线程可以通过检测数组中的节点决定是否对这条链表(红黑树)进行扩容,减小了扩容的粒度,提高了扩容的效率。

下面是我对面试中的那个问题的一下看法。

为什么是synchronized,而不是ReentranLock

(1)减少内存开销

假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。

(2)获得JVM的支持

可重入锁毕竟是API这个级别的,后续的性能优化空间很小。
synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。

  面试题 concurrentHashMap和HashTable有什么区别?

concurrentHashMap融合了hashmap和hashtable的优势,hashmap是不同步的,但是单线程情况下效率高,hashtable是同步的同步情况下保证程序执行的正确性。

concurrentHashMap锁的方式是细粒度的。concurrentHashMap将hash分为16个桶(默认值),诸如get、put、remove等常用操作只锁住当前需要用到的桶。

concurrentHashMap的读取并发,因为读取的大多数时候都没有锁定,所以读取操作几乎是完全的并发操作,只是在求size时才需要锁定整个hash。

而且在迭代时,concurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,弱一致迭代器。在这种方式中,当iterator被创建后集合再发生改变就不会抛出ConcurrentModificationException,取而代之的是在改变时new新的数据而不是影响原来的数据,iterator完成后再讲头指针替代为新的数据,这样iterator时使用的是原来的数据。


4.跳表集合 

跳表是一种类似于平衡二叉树的数据结构,主要在链表中使用

原理:一个有序的数组正常使用索引查询的时候时间复杂度为O(1),但是如果是需要通过遍历查找想要优化查找速度则会使用二分法,而链表不是数组,为了有相似的效果所以引入了跳表集合

跳表的两种集合类型:ConcurrentSkipListMap、ConcurrentSkipListSet,下面依次介绍

1.ConcurrentSkipListMap

常用方法:

方法描述
put(key, value)将指定的键值对插入到映射中。如果键已经存在,则更新对应的值。
get(key)返回与指定键关联的值,如果键不存在,则返回 null。
remove(key)从映射中移除与指定键关联的映射关系。
containsKey(key)判断映射中是否包含指定键。
containsValue(value)判断映射中是否包含指定值。
size()返回映射中键值对的数量。
isEmpty()判断映射是否为空。
clear()从映射中移除所有的键值对。

案例代码:

package Example2127;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

public class javaDemo {
    public static void main(String[] args) throws Exception {
        // 创建子线程优先同步器
        CountDownLatch latch = new CountDownLatch(10);
        Map<String, Integer> map = new ConcurrentHashMap<>();

        for (int i = 0; i < 10; i++) {
            int threadNum = i;
            new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    // 存放数据到map中
                    map.put("Thread" + threadNum + "-" + j, j);
                }
                latch.countDown();
            }, "Thread" + threadNum).start();
        }

        // 等待子线程放入所有数据完毕
        latch.await();

        // 通过跳表的查询,优化查询速度
        System.out.println(map.get("Thread9-5"));
    }
}

 2.ConcurrentSkipListSet

常用的方法:

方法描述
boolean add(E e)向集合中添加指定元素
boolean remove(Object o)从集合中移除指定元素
boolean contains(Object o)判断集合中是否包含指定元素
int size()返回集合的大小
E first()返回集合中的第一个元素
E last()返回集合中的最后一个元素
Iterator<E> iterator()返回在此集合元素上进行迭代的迭代器
void clear()从集合中移除所有元素
Object[] toArray()返回包含集合中所有元素的数组
boolean isEmpty()判断集合是否为空

案例代码:

package Example2128;

import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;

public class javaDemo {
    public static void main(String[] args) {
        Set<String> set = new ConcurrentSkipListSet<>();
        CountDownLatch latch = new CountDownLatch(10);

        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    set.add("Thread" + temp + "-" + j);
                }
                latch.countDown();
            }, "Thread" + temp).start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(set.contains("Thread3-8"));
    }
}

面试题哪些集合类是线程安全的?

  • Vector:就比Arraylist多了个同步化机制(线程安全)。
  • Stack:栈,也是线程安全的,继承于Vector。
  • Hashtable:就比Hashmap多了个线程安全。
  • ConcurrentHashMap:是一种高效但是线程安全的集合。

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

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

相关文章

easyx图形库基础4:贪吃蛇

贪吃蛇 一实现贪吃蛇&#xff1a;1.绘制网格&#xff1a;1.绘制蛇&#xff1a;3.控制蛇的默认移动向右&#xff1a;4.控制蛇的移动方向&#xff1a;5.生成食物6.判断蛇吃到食物并且长大。7.判断游戏结束&#xff1a;8.重置函数&#xff1a; 二整体代码&#xff1a; 一实现贪吃蛇…

今年七夕情人节,要送数码产品给对象?这几款送人不出错的数码产品

时间过的还挺快的又到了今年的七夕情人节了&#xff0c;你是否还在为送什么数码产品给对象而犯愁&#xff1f;做过功课挑选的数码好物肯定会让TA十分惊喜&#xff0c;作为一个数码发烧友&#xff0c;我盘点了几款适合送对象的数码好物&#xff0c;大家可以甄选看看。 第一款&a…

LeetCode--HOT100题(32)

目录 题目描述&#xff1a;138. 复制带随机指针的链表&#xff08;中等&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;138. 复制带随机指针的链表&#xff08;中等&#xff09; 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &…

【C++】位图与布隆过滤器(内含相关高频面试题)

本篇文章会对位图和布隆过滤器进行详解。同时还会给出位图和布隆过滤器相关的高频面试题与解答。希望本篇文章会对你有所帮助。 文章目录 一、位图的引入 1、1 查找整数&#xff08;腾讯面试题&#xff09; 1、2 解决方法1 1、3 解决方法2 1、3、1 外部排序 二、位图的原理与…

电压调整器之LDO稳压器电路 士兰微SA1117B系列SA1117BH-ADJTR

关于LDO调节器&#xff08;Low Dropout Regulator&#xff09;是一种电压稳压器件&#xff0c;常用于电子设备中&#xff0c;用于将高电压转换为稳定的低电压。它能够在输入电压和输出电压之间产生较小的差异电压&#xff0c;因此被称为"低压差稳压器"。 LDO调节器通…

Llama 2免费托管及API提供

Llama 2 是 Meta 最新的文本生成模型&#xff0c;目前其性能优于所有开源替代方案。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、强大的Llama 2 它击败了 Falcon-40B&#xff08;之前最好的开源基础模型&#xff09;&#xff0c;与 GPT-3.5 相当&#xff0c;仅低…

禁止特斯拉入内 — 智能驾驶引发的“争议”与“合规”之路

近日&#xff0c;湖南岳阳三荷机场停车场禁止特斯拉入内引发关注&#xff0c;原因在于车主离开车辆后&#xff0c;特斯拉自带的哨兵模式会对车身周边环境进行录像。实际上&#xff0c;之前就有网友提到军事禁区、党政机关、重点政府单位、重点国有企业等也是不允许特斯拉进入。…

电商增强现实3D模型优化需要关注的4个方面

到目前为止&#xff0c;AR技术已经发展到足以在更广泛的范围内实施。 在电子商务中&#xff0c;这项技术有望提供更令人兴奋的购物体验。 为了实现这一目标&#xff0c;在这篇博客中&#xff0c;我将介绍如何针对电子商务中的 AR 优化 3D 模型。 推荐&#xff1a;用 NSDT编辑器…

【hadoop】windows上hadoop环境的搭建步骤

文章目录 前言基础环境下载hadoop安装包下载hadoop在windows中的依赖配置环境变量 Hadoop hdfs搭建创建hadfs数据目录修改JAVA依赖修改配置文件初始化hdfs namenode启动hdfs 前言 在大数据开发领域中&#xff0c;不得不说说传统经典的hadoop基础计算框架。一般我们都会将hadoo…

LeetCode 160.相交链表

文章目录 &#x1f4a1;题目分析&#x1f4a1;解题思路&#x1f6a9;步骤一&#xff1a;找尾节点&#x1f6a9;步骤二&#xff1a;判断尾节点是否相等&#x1f6a9;步骤三&#xff1a;找交点&#x1f344;思路1&#x1f344;思路2 &#x1f514;接口源码 题目链接&#x1f449;…

九五从零开始的运维之路(其三十五)

文章目录 前言一、概述1.概念2.组成3.特点4.工作原理5.优点&#xff1a; 二、各节点及其ip地址三、构建MHA1.ssh免密登录2.构建mysql主从复制&#xff08;一&#xff09;安装mariadb数据库并启动&#xff08;二&#xff09;master服务器&#xff08;三&#xff09;slave服务器&…

【Java转Go】快速上手学习笔记(二)之基础篇一

目录 创建项目数据类型变量常量类型转换计数器键盘交互流程控制代码运算符 创建项目 上篇我们安装好了Go环境&#xff0c;和用IDEA安装Go插件来开发Go项目&#xff1a;【Java转Go】快速上手学习笔记&#xff08;一&#xff09;之环境安装篇 。 这篇我们开始正式学习Go语言。我…

【数据结构OJ题】链表中倒数第k个结点

原题链接&#xff1a;https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId13&&tqId11167&rp2&ru/activity/oj&qru/ta/coding-interviews/question-ranking 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 …

数据结构的树存储结构

数据结构的树存储结构 之前介绍的所有的数据结构都是线性存储结构。本章所介绍的树结构是一种非线性存储结构&#xff0c;存储的是具有“一对多”关系的数据元素的集合。 (A) (B) 图 1 树的示例 图 …

vue树状结构以及设计思路

设计思路&#xff1a;多级数组循环遍历&#xff0c;第一层样式加三角形折叠&#xff0c;第二层在文字前方加 —&#xff08;横线&#xff09;&#xff0c;第三层加横线&#xff0c;第四层加点。给第二层第三层左侧加左边框&#xff0c;用translateY进行位移就形成了树状样式。 …

《起风了》C++源代码

使用方法 Visual Studio、Dev-C、Visual Studio Code等C/C创建一个 .cpp 文件&#xff0c;直接粘贴赋值即可。 #include <iostream> #include <Windows.h> #pragma comment(lib,"winmm.lib") using namespace std; enum Scale {Rest 0, C8 108, B7 …

SpringBoot08——前端数据模拟MockJS+vue-element-admin后台集成

感觉用到再说吧 2. vue-element-admin后台集成 3.JWT跨域认证 看自己的demo2源码吧

好用的networkx绘图包

1. NetworkX简介 NetworkX 是一个用于创建、操作和研究复杂网络的 Python 库。它可以创建、分析和可视化各种类型的网络(包括有向图和无向图)&#xff0c;例如社交网络、Web图、生物网络等。 NetworkX 提供了许多图的算法和分析工具&#xff0c;比如节点的度、网络的直径、最短…

jvm内存溢出排查(使用idea自带的内存泄漏分析工具)

文章目录 1.确保生成内存溢出文件2.使用idea自带的内存泄漏分析工具3.具体实验一下 1.确保生成内存溢出文件 想分析堆内存溢出&#xff0c;一定在运行jar包时就写上参数-XX:HeapDumpOnOutOfMemoryError&#xff0c;可以看我之前关于如何运行jar包的文章。若你没有写。可以写上…

express学习笔记8 - 文件上传 下载以及预览

一、上传 1、 安装multer (任意选其中一种) yarn add multer --S npm install multer --S 2、新建配置文件(utils/multerConfig) const multer require(multer); const mkdirp require(mkdirp); // const sd require(silly-datetime); const path require(path);con…