[Java EE] 多线程进阶(3) [线程安全集合类]

五.Java 中线程安全的集合类

线程安全的集合类 , 其核心解决普通集合(如 ArrayList/HashMap)在并发读写时的数据错乱 , ConcurrentMidificationException , 此循环等问题

1.线程安全集合的核心分类

|--------------|---------------------------------|------------------------------------------------------------------|-------------------------------|
| 分类 | 实现原理 | 代表类 | 核心特点 |
| 同步包装类 | Collections.synchronizedXxx() | synchronizedList/synchronizedMap | 全局加锁(synchronized),简单但并发性能低 |
| JUC 并发集合 | 分段锁 / CAS / 写时复制 / 阻塞队列 | ConcurrentHashMap/CopyOnWriteArrayList/LinkedBlockingQueue | 精细化锁 / 无锁设计,高并发下性能更优 |

2.多线程下使用 ArrayList

① 自己使用同步机制(synchronized 或 ReentrantLock)

此处不再说

② 通过 Collection 工具类为普通集合套上 [ 全局锁 ]

本质是对集合所有方法加 synchronized

java 复制代码
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

注意 : 迭代时需要手动加锁 , 否则抛异常(ConcurrentMidificationException)

java 复制代码
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        //多线程下的ArraryList
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        syncList.add("Hello");
        syncList.add("World");
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncList.add("Java");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncList.add("Thread");
            }
        }).start();
        Thread.sleep(1000);
        synchronized (syncList) {//必须锁定集合对象本身
            for (String s : syncList) {
                System.out.println(s);
            }

        }

    }
}

③ 使用 JUC 包 , CopyOnWriteArrayList

核心实现 :

  • 写操作 : (add/remove/set) 复制一份新的数组 , 在数组上修改 , 修改完成后替换原数组 , 全程加锁
  • 读操作 : (get/iterator) 直接读原数组 , 无锁 , 性能极高

常见问题 :

问题 1 : 写操作性能地 , 内存开销大

解决方案 : 仅用于都铎写少的场景 ; 当频繁场景时改用 ReentrantLock 手动加锁的普通 List

问题 2 : 迭代过程中其他线程的修改不会反映到迭代器中

解决方案 : 需要加锁 ; 或者每次迭代前重新获取集合

3.多线程使用队列

① ArrayBlockingQueue

基于数组实现的阻塞队列

② LinkedBlockingQueue

基于链表实现的阻塞队列

③ PriorityBlockingQueue

基于堆实现的带优先级的阻塞队列

④ TransferQueue

最多只包含一个元素的阻塞队列

4.多线程使用哈希表

在多线程下使用哈希表(键值对储存) , 核心问题是解决线程安全和并发性能--普通 HashMap 线程不安全 , 并发读写会导致数据错乱 , 死循环 , ConcurrentModificationException 等问题

① 线程安全哈希表

|---------|--------------------------------------------|------------------------------------------|
| 类型 | 代表类 | 实现原理 |
| 全局锁哈希表 | Hashtable/ Collections.synchronizedMap | 所有方法加 synchronized;全局锁 |
| 精细化锁哈希表 | ConcurrentHashMap | JDK1.7:分段锁;JDK1.8:CAS + 局部synchronized |
| 写时复制哈希表 | ConcurrentSkipListMap(有序) | 跳表 + CAS,无锁设计 |

②Hashtable(低效)

只是简单的把关键的方法加上了 synchronized

这相当于直接对 Hashtable 对象本身加锁

  • 如果多线程访问同一个 Hashtable 就会直接造成锁冲突
  • 一旦触发扩容 , 就由该线程完成整个扩容过程

注意 : Hashtable 是 JDK1.0 的老旧类 , 性能差 , 已被ConcurrentHashMap 完全替代 , 仅作为了解

②ConcurrentHashMap(首选)

核心原理: 抛弃了 JDK1.7 的分段锁(把这些链表分成几组 , 每个组安排一个锁) , 改用更细粒度的锁机制 (锁桶)

  • 读操作 : 无锁 , 通过 volatile 保证数据可见性
  • 写操作 : 1)对空桶 : CAS 原子操作写入 , 无锁 ; 2) 对非空桶 : 对桶首届点加 synchronized 锁 , 仅阻塞当前桶的读写 , 其他桶可并发
  • 当桶元素超过 8 个转为红黑树 , 提升查找性能
java 复制代码
private static final Map<String, Integer> map = new ConcurrentHashMap<>();
扩容机制 : 化整为零
  • 发现扩容的线程只创建新数组,搬几个元素 : 1) 触发扩容的线程先标记 , 并且创建新数组(容量翻倍) ; 2) 按步长 (16)拆分原数组 , 该线程仅迁移自己负责的一小段桶(目的:避免的那现场一次迁移所有元素导致长时间阻塞)
  • 扩容期间新老数组同时存在 : 1)新数组作为全局变量 , 扩容全程与老数组共存; 2)迁移完成的桶做标记 , 未迁移的桶仍在老数组中(保证连续性)
  • 后续线程搬家 , 各搬一小部分 : 1)任何线程执行 put/remove 时 , 若检测到扩容中 , 会先暂停自身操作 , 协助迁移 ; 2)每个线程仅迁移自己"认领的桶段"(避免重复)
  • 搬完最后一个元素删除老数组 : 1)所有桶迁移完成 , 主线程将老数组替换成新数组 , 并删除 (释放内存); 2)更新扩容阈值 , 并标记扩容完成
  • 插入只往新数组加 : 扩容期间 , put 操作先检查桶是否已经迁移 , 若已迁移 : 直接写入新数组 , 若未迁移 : 先协助迁移 , 再写入新数组(防止老数组数据冗余)
  • 查找需同时查找新老数组 : get 操作 , 先查老数组 , 若桶已迁移完(标记) , 则跳转到行数则查询 ; 若桶未迁移 : 直接查询老数组 ; 若新老数组都查不到 , 再返回 null(保证扩容期间读取数据不丢失 , 不遗漏)
相关推荐
洲星河ZXH22 分钟前
Java,日期时间API
java·开发语言·python
前端老曹23 分钟前
Jspreadsheet CE V5 使用手册(保姆版) 二
开发语言·前端·vue.js·学习
陈聪.24 分钟前
MySQL全平台安装指南:Windows与Linux详细教程
linux·windows·mysql
秋邱24 分钟前
AR 定位技术深度解析:从 GPS 到视觉 SLAM 的轻量化实现
开发语言·前端·网络·人工智能·python·html·ar
雨疏风骤124029 分钟前
【FreeRTOS】任务、任务状态
开发语言·stm32·c#·rtos
SongYuLong的博客33 分钟前
开源 C 标准库(C Library)
c语言·开发语言·开源
GHL28427109033 分钟前
win32给进程设置图标、修改图标
c++·windows
木棉知行者34 分钟前
(二)Python基本语句
开发语言·python
傻啦嘿哟35 分钟前
2026版基于Python的旅游景点推荐系统:技术解析与实现路径
开发语言·python