并发编程之并发容器

目录

并发容器

CopyOnWriteArrayList

应用场景

常用方法

读多写少场景使用CopyOnWriteArrayList举例

  CopyOnWriteArrayList原理

CopyOnWriteArrayList 的缺陷

扩展迭代器fail-fast与fail-safe机制

ConcurrentHashMap

应用场景

常用方法

并发场景下线程安全举例

ConcurrentHashMap底层结构

 ConcurrentSkipListMap

应用场景

常用方法

跳表


并发容器

        java中常用的集合ArrayList、LinkedList、HashMap这些容器都是非线程安全的。Java先提供了同步容器供使用。同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector、Hashtable以及SynchronizedList等容器。这样做的代价是削弱了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会降低。

       为了解决同步容器的性能问题,有了并发容器,java.util.concurrent包中提供了多种并发类容器。以下以部分并发容器详细介绍。

CopyOnWriteArrayList

应用场景

1. 读多写少的情况

      当对列表的读操作远远多于写操作时,CopyOnWriteArrayList可以提供良好的性能。由于读操作不涉及锁,多个线程可以同时读取,而写操作只需要在复制时进行同步。

2. 避免读写冲突

       在某些情况下,使用传统的同步容器可能会导致读写冲突,因为读取和写入操作同时进行时可能会引发竞态条件。CopyOnWriteArrayList避免了这种冲突,通过牺牲内存来保证读取的一致性。

3. 不需要实时更新的情况

       如果对实时性要求不高,即时写操作会导致列表被复制,但读取操作仍然可以在原始列表上进行,从而保持一致性。

4. 稳定的数据集

       当数据集相对稳定,且写操作相对较少时,CopyOnWriteArrayList是一个合适的选择。写操作不频繁的情况下,复制列表的开销相对较小。

常用方法
CopyOnWriteArrayList copyOnWriteArrayList= new CopyOnWriteArrayList();
// 新增
copyOnWriteArrayList.add(1);
// 设置(指定下标)
copyOnWriteArrayList.set(0, 2);
// 获取(查询)
copyOnWriteArrayList.get(0);
// 删除
copyOnWriteArrayList.remove(0);
// 清空
copyOnWriteArrayList.clear();
// 是否为空
copyOnWriteArrayList.isEmpty();
// 是否包含
copyOnWriteArrayList.contains(1);
// 获取元素个数
copyOnWriteArrayList.size();
读多写少场景使用CopyOnWriteArrayList举例

       假设有一个任务调度器,其中包含了多个定时任务,每个任务都有一个执行时间。多个线程同时读取任务列表以查看下一个要执行的任务,而写入操作仅在添加、删除任务时发生。 

import java.util.concurrent.CopyOnWriteArrayList;

public class TaskScheduler {
    // 使用CopyOnWriteArrayList作为任务列表
    private CopyOnWriteArrayList<Task> taskList = new CopyOnWriteArrayList<>();
    // 添加任务
    public void addTask(Task task) {
        taskList.add(task);
    }
    // 删除任务
    public void removeTask(Task task) {
        taskList.remove(task);
    }
    // 获取下一个要执行的任务
    public Task getNextTask() {
        if (!taskList.isEmpty()) {
            // 获取第一个任务(最早执行的任务)
            return taskList.get(0);
        }
        return null;
    }

    // 其他与任务调度相关的操作
    // ...
}

class Task {
    private String name;
    private long executionTime;
    // 构造方法、getter、setter等
    // ...
}
  CopyOnWriteArrayList原理

       CopyOnWriteArrayList 内部使用了一种称为“写时复制”的机制。当需要进行写操作时,它会创建一个新的数组,并将原始数组的内容复制到新数组中,然后进行写操作。因此,读操作不会被写操作阻塞,读操作返回的结果可能不是最新的,但是对于许多应用场景来说,这是可以接受的。此外,由于读操作不需要加锁,因此它可以支持更高的并发度。

            

CopyOnWriteArrayList 的缺陷

1. 内存开销大
       每次写入操作都会创建一个列表的副本,这导致了较大的内存开销。在数据集很大的情况下,频繁的写入操作可能占用大量内存,这可能成为一个问题。
2. 不适合写入频繁的场景
        如果写入操作非常频繁,复制列表的开销可能会变得显著,并且可能会导致性能问题。适用于写多读少或写入相对较少的场景。
3. 不适合实时性要求高的场景
       由于写入操作会导致复制整个列表,而且复制操作不是原子的,因此在写入过程中,读取操作可能会看到部分修改或者不一致的状态。这使得 CopyOnWriteArrayList 不适合对实时性要求很高的场景。
4. 不支持并发修改迭代
       CopyOnWriteArrayList 的迭代器是不可修改的,因此在使用迭代器遍历列表时,不允许修改列表。虽然这是为了确保线程安全设计的,但对于某些应用可能会限制灵活性。
5. 不适用于元素更新频繁的情况
       如果在列表中的元素需要频繁更新,CopyOnWriteArrayList 可能不是最佳选择,因为每次更新都会导致整个列表的复制,开销较大。


扩展迭代器fail-fast与fail-safe机制

fail-fast 机制

       fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了。那么线程A访问集合时,就会抛出 ConcurrentModificationException异常,产生fail-fast事件。

fail-fast解决方案

方案一

       在遍历过程中所有涉及到改变modCount 值的地方全部加上synchronized 或者直接使用 Collection#synchronizedList,这样就可以解决问题,但是不推荐,因为增删造成的同步锁可能会阻塞遍历操 作。

方案二

       使用CopyOnWriteArrayList 替换 ArrayList,推荐使用该方案(即fail-safe)。

fail-safe机制

       任何对集合结构的修改都会在一个复制的集合上进行,因此不会抛出ConcurrentModificationException。在 java.util.concurrent 包中的集合,如  CopyOnWriteArrayList、ConcurrentHashMap 等,它们的迭代器一般都是采用 Fail-Safe 机制。

缺点

1. 采用 Fail-Safe 机制的集合类都是线程安全的,但是它们无法保证数据的实时一致性,它们只能保证数据的最终 一致性。在迭代过程中,如果集合被修改了,可能读取到的仍然是旧的数据。

2. Fail-Safe 机制还存在另外一个问题,就是内存占用。由于这类集合一般都是通过复制来实现读写分离的,因此它 们会创建出更多的对象,导致占用更多的内存,甚至可能引起频繁的垃圾回收,严重影响性能。


ConcurrentHashMap

        ConcurrentHashMap是Java中线程安全的哈希表,它支持高并发并且能够同时进行读写操作。 在JDK1.8之前,ConcurrentHashMap使用分段锁以在保证线程安全的同时获得更大的效率。JDK1.8开始舍弃了分段锁,使用自旋+CAS+synchronized关键字来实现同步。官方的解释中:一是节省内存空间 ,二是分段锁需要更多的内存空间。

应用场景

1. 高并发的数据存储
       当多个线程需要同时访问和修改一个共享的哈希表时,ConcurrentHashMap 提供了一种线程安全的方式,避免了在读取和写入操作上的锁竞争。
2. 缓存管理
        用作缓存的存储结构,特别是在读多写少的情况下。由于 ConcurrentHashMap 具有分段锁机制,多个线程可以并发地访问不同的段,从而提高并发性能。
3. 并发计数器
        当需要进行多线程并发计数时,可以使用 ConcurrentHashMap 的原子操作方法,如 compute、merge 等,以确保计数的一致性。
4. 分布式系统中的协调
        在分布式系统中,ConcurrentHashMap 可以用于协调不同节点之间的数据共享。它提供了一种线程安全的方式来管理共享数据,以避免并发访问问题。
5. 任务调度和执行管理
       在多线程的任务调度和执行场景中,ConcurrentHashMap 可以用来存储和管理任务的状态、结果等信息,确保在并发情况下的正确性。
6. 实时性要求不高的场景
       相较于传统的 HashMap,ConcurrentHashMap 在保证线程安全的同时,提供了更高的并发性。因此,在对实时性要求不是非常高的场景下,可以使用它来提高并发性能。
7. 在线程池中的任务管理
       当多个线程池中的任务需要共享数据时,ConcurrentHashMap 可以作为一个线程安全的数据结构来确保并发访问的正确性。

常用方法
 // 创建一个 ConcurrentHashMap 对象
ConcurrentHashMap<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();
// 添加键值对
concurrentHashMap.put("key", "value");
// 添加一批键值对
concurrentHashMap.putAll(new HashMap());
// 使用指定的键获取值
concurrentHashMap.get("key");
// 判定是否为空
 concurrentHashMap.isEmpty();
// 获取已经添加的键值对个数
concurrentHashMap.size();
// 获取已经添加的所有键的集合
concurrentHashMap.keys();
// 获取已经添加的所有值的集合
concurrentHashMap.values();
// 清空
concurrentHashMap.clear();
并发场景下线程安全举例
import java.util.concurrent.ConcurrentHashMap;

public class CacheManager {
    // 使用ConcurrentHashMap作为缓存存储
    private static ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        // 模拟多个线程同时访问缓存
        for (int i = 0; i < 5; i++) {
            String key = "Key" + i;
            Thread readerThread = new Thread(() -> readFromCache(key));
            readerThread.start();

            String newKey = "NewKey" + i;
            String value = "Value" + i;
            Thread writerThread = new Thread(() -> addToCache(newKey, value));
            writerThread.start();
        }
    }

    // 从缓存读取数据的方法
    private static void readFromCache(String key) {
        // 使用get方法从缓存读取数据
        String value = cache.get(key);
        System.out.println(Thread.currentThread().getName() + ": Reading from cache - Key: " + key + ", Value: " + value);
    }

    // 向缓存添加数据的方法
    private static void addToCache(String key, String value) {
        // 使用put方法向缓存添加数据
        cache.put(key, value);
        System.out.println(Thread.currentThread().getName() + ": Adding to cache - Key: " + key + ", Value: " + value);
    }
}

         在上述案例中,多个线程可以同时读取和写入缓存项,而ConcurrentHashMap会确保在多线程并发访问的情况下不会发生数据不一致或线程安全问题。

ConcurrentHashMap底层结构

在jdk1.7及其以下的版本中,结构是用Segments数组 + HashEntry数组 + 链表实现的。

      

jdk1.8抛弃了Segments分段锁的方案,而是改用了和HashMap一样的结构操作,也就是数组 + 链表 + 红黑树结构,比jdk1.7中的ConcurrentHashMap提高了效率,在并发方面,使用了cas + synchronized的方式保证数据的一致性。

        

链表转化为红黑树需要满足2个条件:

1. 链表的节点数量大于等于树化阈值8

2. Node数组的长度大于等于最小树化容量值64

#树化阈值为8
static final int TREEIFY_THRESHOLD = 8;
#最小树化容量值为64
static final int MIN_TREEIFY_CAPACITY = 64;

 ConcurrentSkipListMap

       ConcurrentSkipListMap 是 Java 中的一种线程安全、基于跳表实现的有序映射(Map)数据结构。 它是对 TreeMap 的并发实现,支持高并发读写操作。 ConcurrentSkipListMap适用于需要高并发性能、支持有序性和区间查询的场景,能够有效地提高系 统的性能和可扩展性。

应用场景

1. 并发映射
       
ConcurrentSkipListMap 实现了 ConcurrentMap 接口,可以被用作并发映射(key-value映射)的数据结构。多个线程可以安全地同时访问和修改映射。
2. 有序性要求
      
 由于底层是基于跳表实现的,ConcurrentSkipListMap 中的元素是有序的。如果你需要按照键的自然顺序或者自定义的比较器顺序来存储和访问数据,这个类就是一个很好的选择。
3. 高并发读写
      
 跳表的结构允许多个线程并发地进行读取和写入操作,而不会导致锁竞争。这使得 ConcurrentSkipListMap 在高并发环境下的读写性能表现相对较好。
4. 范围查询
     
  跳表的结构使得范围查询变得高效。ConcurrentSkipListMap 提供了一些方法,如 subMap,可以用于获取键范围内的子映射。
5. 替代传统的并发映射实现
  
     在某些情况下,ConcurrentSkipListMap 可以作为对传统的基于锁的并发映射实现的一种替代。它的设计允许更高的并发性。

常用方法
添加元素:
put(K key, V value): 将指定的键值对添加到映射中。
putIfAbsent(K key, V value): 如果指定的键尚未与值关联,则将其与给定的值关联。

获取元素:
get(Object key): 返回指定键所映射的值,如果该键不存在,则返回 null。
firstKey(): 返回映射中的第一个(最小的)键。
lastKey(): 返回映射中的最后一个(最大的)键。
lowerKey(K key): 返回严格小于给定键的最大键,如果不存在则返回 null。
higherKey(K key): 返回严格大于给定键的最小键,如果不存在则返回 null。

删除元素:
remove(Object key): 从映射中删除指定键的映射。
pollFirstEntry(): 移除并返回映射中的第一个(最小的)映射。
pollLastEntry(): 移除并返回映射中的最后一个(最大的)映射。

范围查询:
subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive): 返回映射中键的部分视图,其键范围从 fromKey 到 toKey。

遍历:
entrySet(): 返回包含映射中所有映射的 Set 视图。
keySet(): 返回包含映射中所有键的 NavigableSet 视图。
values(): 返回包含映射中所有值的 Collection 视图。

其他操作:
size(): 返回映射中的键值对数量。
isEmpty(): 如果映射不包含键值对,则返回 true。
clear(): 从映射中移除所有映射。
跳表

跳表是一种基于有序链表的数据结构,支持快速插入、删除、查找操作,其时间复杂度为O(log n), 比普通链表的O(n)更高效。

 跳表的特性

一个跳表结构由很多层数据结构组成。每一层都是一个有序的链表,默认是升序。也可以自定义排序方法。 最底层链表(图中所示Level1)包含了所有的元素。 如果每一个元素出现在LevelN的链表中(N>1),那么这个元素必定在下层链表出现。 每一个节点都包含了两个指针,一个指向同一级链表中的下一个元素,一个指向下一层级别链表中的相同值元素。

跳表的查找

 跳表的插入

跳表插入数据的流程如下:

找到元素适合的插入层级K,这里的K采用随机的方式。若K大于跳表的总层级,那么开辟新的一层,否则在对应 的层级插入。 申请新的节点。 调整对应的指针。 

假设我要插入元素13,原有的层级是3级,假设K=4:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件测试|教你使用Python下载图片

前言 我一直觉得Windows系统默认的桌面背景不好看&#xff0c;但是自己又没有好的资源可以进行替换&#xff0c;突然我一个朋友提醒了我&#xff0c;网络上的图片这么多&#xff0c;你甚至可以每天换很多个好看的背景&#xff0c;但是如果让我手动去设置的话&#xff0c;我觉得…

编程高手必备:Python字典操作与示例全解析

编程高手必备&#xff1a;Python字典操作与示例全解析 引言Python字典基础字典操作技巧代码示例实际应用案例实际应用案例结语 引言 在编程世界中&#xff0c;高效地处理和组织数据是每位程序员必备的技能。Python&#xff0c;作为一种广受欢迎的编程语言&#xff0c;不仅因其…

mysql原理--undo日志1

1.事务回滚的需求 我们说过 事务 需要保证 原子性 &#xff0c;也就是事务中的操作要么全部完成&#xff0c;要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况&#xff0c;比如&#xff1a; (1). 事务执行过程中可能遇到各种错误&#xff0c;比如服务器本身的错误&…

Jenkins-用户管理

用户管理 1 安装插件 2 选择安全策略为刚刚安装的插件 3 这个是安装插件以后会有的选项 4 增加一个角色 5 根据需要赋值角色的权限&#xff0c;并分配给用户

橘子学Mybatis07之Mybatis关于缓存的设计

很逆天的一件事是&#xff0c;我上一次发mybatis是在2022年10月15号&#xff0c;然后直到今天才开始总结下一篇Mybatis的东西。一年里面忙成那啥了&#xff0c;而且重心都投入在了Elasticsearch的学习上面&#xff0c;基本一年下来都在搞ES&#xff0c;并且考下了ECE认证&#…

【Docker】Dockerfile构建最小镜像

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 前言 一.Dockerfile是什么 二.Dock…

lenovo联想笔记本电脑拯救者Legion Y7000 2019 PG0(81T0)原装出厂Windows10系统

链接&#xff1a;https://pan.baidu.com/s/1fn0aStc4sfAfgyOKtMiCCA?pwdas1l 提取码&#xff1a;as1l 联想拯救者原厂Win10系统自带所有驱动、出厂主题壁纸、系统属性联机支持标志、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;…

模型索引:QModelIndex

一、为什么要使用模型索引&#xff1f; 从名字可以看出&#xff0c;他是模型的索引&#xff0c;只要对模型实体&#xff08;各种xxxModel的实体&#xff09;施加这个索引&#xff0c;model就会返回数据集中对应的值&#xff0c;或者通过这个索引修改对应数据集中的值。 类比数…

VMware安装CentOS7虚拟机

VMware 安装 获取 VMware 安装包 下载地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1ELR5NZa7rO6YVplZ1IUigw?pwdplz3 提取码&#xff1a;plz3 包括&#xff1a;当然&#xff0c;也可以自己去别的地方下载&#xff0c;WMware 版本都差不多&#xff0c;现在用的比…

数据结构学习 jz14剪绳子

关键词&#xff1a;数学 动态规划 快速幂 这道题其实是分为两题。 题目一&#xff1a; 这道题我是没有思路的&#xff0c;看了k神的答案才知道有数学的方法。 方法一&#xff1a; 数学&#xff1a;其实中间的推导我没看&#xff0c;我服了。 思路&#xff1a; 复杂度计算&…

matplotlib:热图、箱形图、小提琴图、堆叠面积图、雷达图、子图

简介&#xff1a;在数字化的世界里&#xff0c;从Web、HTTP到App&#xff0c;数据无处不在。但如何将这些复杂的数据转化为直观、易懂的信息&#xff1f;本文将介绍六种数据可视化方法&#xff0c;帮助你更好地理解和呈现数据。 热图 (Heatmap)&#xff1a;热图能有效展示用户…

WorkPlus企业内部即时通信新选择,打造高效协作新格局

在企业内部&#xff0c;快速、高效的沟通与协作是推动工作进程的关键。而即时通信工具成为了企业内部沟通的重要工具。作为一款优秀的企业内部即时通信工具&#xff0c;WorkPlus通过其出色的性能和独特的功能&#xff0c;为企业打造高效协作的新格局。 为什么选择WorkPlus作为企…

详解Matlab深度学习进行波形分割

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 &#x1f510;#### 防伪水印——左手の明天 ####&#x1f510; &#x1f497; 大家…

强化学习应用(三):基于Q-learning算法的无人车配送路径规划(提供Python代码)

一、Q-learning算法介绍 Q-learning是一种强化学习算法&#xff0c;用于解决基于环境的决策问题。它通过学习一个Q-table来指导智能体在不同状态下采取最优动作。下面是Q-learning算法的基本步骤&#xff1a; 1. 定义环境&#xff1a;确定问题的状态和动作空间&#xff0c;并…

逆变器之推挽谐振

首先把前级推挽电路分成几个模块&#xff1a;方波发生器、谐振LC、整流滤波以及负载。框图如下图所示&#xff1a; 分析前提&#xff1a;稳态 在推挽电路正常工作中&#xff0c;输入电压恒定、输出电流电压也恒定&#xff08;电源处于稳定的工作状态中&#xff09; 方波发生器…

Objective-C使用UISwitch控制UITextField显示明文或密文

1.xib中设计 2.关联控件 3.使用代码控制开关与TextField显示模式 4.开关控件UISwitch点击事件实现,点击时根据状态切换TextField显示模式 5.显示效果:

【Qt】QThread moveTothread-多线程的两种实现方法

一、如何理解多线程 二、实现多线程的两种方式&#xff08;面向应用&#xff09; 2.1 继承 QThread 的类 2.2 (推荐这种方式)函数 moveTothread() 三、多线程的释放问题&#xff08;善后工作&#xff09; 多线程的两种实现方法 一、如何理解多线程二、实现多线程的两种方式&…

文件操作(二)

͟͟͞͞&#x1f3c0;前言上一篇我们加们讲了什么是文件&#xff0c;为什么使用文件&#xff0c;以及流的概念。我们继续接上一篇来继续讲解我们的文件操作&#xff0c;这一篇将会详细的讲如何对文件进行读写。 目录 &#x1f680;一.文件的顺序读写 1.fgetc和fputc 2.fget…

oracle—IMU机制

正常的情况下&#xff0c;当事务需要回滚块的时候&#xff0c;是去undo表空间找 现在是在sharepool中分一个IMUbuffer&#xff0c;将所有的回滚信息写入。直接就可以从中取。减少了物理IO 同时这个过程也产生redo&#xff0c;直接就是图中红色的&#xff0c;不防止崩溃 优点 1…

Java21 + SpringBoot3集成WebSocket

文章目录 前言相关技术简介什么是WebSocketWebSocket的原理WebSocket与HTTP协议的关系WebSocket优点WebSocket应用场景 实现方式1. 添加maven依赖2. 添加WebSocket配置类&#xff0c;定义ServerEndpointExporter Bean3. 定义WebSocket Endpoint4. 前端创建WebSocket对象 总结 前…