Erlo

Java 线程安全的集合

2025-03-11 19:29:28 发布   4 浏览  
页面报错/反馈
收藏 点赞

Vector

ArrayList 的线程安全版本,对所有的修改方法都进行了 synchronized 同步处理。适用于多线程环境下对数据一致性要求高,且读写操作相对比较均衡,不需要很高并发性能的场景。由于所有操作都进行同步,在高并发环境下,性能相对较差


Hashtable

HashMap 的线程安全版本,对每个关键方法都进行了 synchronized 同步处理。在多线程环境下需要一个线程安全的键值对存储结构,并且对数据的读写频率相对均衡时适用。高并发下性能不佳,并且不允许键或值为 null


ConcurrentHashMap

1. 数据结构

在 JDK1.7 中,ConcurrentHashMap 底层数据结构由多个 Segment 组成,Segment 继承自 ReentrantLock,本质上是一个可重入锁,每个 Segment 独立管理一部分数据,相当于一个小型的哈希表。每个 Segment 内部包含一个 HashEntry 数组,用于存储键值对。HashEntry 是一个链表结构,用于解决哈希冲突,新的键值对会插入到链表头部

在 JDK1.8 中,ConcurrentHashMap 底层数据结构是一个 Node 数组,Node 节点用链表或红黑树解决哈希冲突。当链表长度小于等于 8 时,采用链表存储数据。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树

2. 读取原理

在 JDK1.7 中,HashEntry 的 value 和 next 指针都被声明为 volatile 类型,保证内存可见性。不同的 Segment 可以并发访问,多个线程可以同时读取不同 Segment 的数据

在 JDK1.8 中,Node 节点的 value 和 next 指针都被声明为 volatile 类型,保证内存可见性。多个线程可同时读取不同位置的元素,提高并发读性能

3. 写入原理

在 JDK1.7 中,首先需要根据键的哈希值定位到对应的 Segment,然后获取该 Segment的锁。获取锁后,在对应 Segment 的 HashEntry 数组中找到合适的位置,将新的键值对插入到链表头部

在 JDK1.8 中,首先需要根据键的哈希值定位到对应的 Node 数组索引,若该位置为空,利用 CAS 操作将新节点插入,成功则插入完成。若 CAS 插入失败,说明发生哈希冲突。当发生哈希冲突时,若是链表,则对链表头节点加锁,遍历链表插入或更新,若是红黑树,则对红黑树的根节点加锁,按红黑树规则插入或更新

4. 扩容机制

在 JDK1.7 中,当某个 Segment 的 HashEntry 数组的元素数量达到阈值时,该 Segment 会加锁进行扩容操作。扩容时会创建一个新的更大的 HashEntry 数组,然后将原数组中的元素重新哈希并复制到新数组。不同的 Segment 可以独立进行扩容,不会影响其他 Segment 的正常操作

在 JDK1.8 中,当元素数量达到阈值,触发扩容。首先使用 CAS 操作创建一个更大的 Node 数组,然后使用 CAS 操作更新数组中的 Node 节点引用,再对 Node 节点的链表头节点或红黑树根节点使用 synchronized 关键字加锁,进行数据迁移操作。ConcurrentHashMap 采用多线程分段迁移的方式将原数组元素迁移到新数组,不同线程可负责不同段的迁移工作


CopyOnWriteArrayList / CopyOnWriteArraySet

CopyOnWriteArrayList 是一种线程安全的 List 实现,允许在多线程环境下进行并发的读写操作,其核心思想是“写时复制”,即当进行写操作时,会创建一个原数据结构的副本,在副本上进行修改,完成后再将副本替换原数据结构。CopyOnWriteArraySet 与 CopyOnWriteArrayList 的作用与实现类似

当执行写操作时,CopyOnWriteArrayList 会先创建一个当前数组的副本,对副本进行写操作。由于操作的是副本,不会影响到其他线程对原数组的读操作,从而保证了读写之间的并发安全。完成对副本的写操作后,会通过原子操作将原数组的引用替换为指向新的副本数组的引用。在这个替换过程中,使用 volatile 关键字修饰数组引用,保证其他线程能够及时看到更新后的数组。上述整个过程使用 ReentrantLock 锁保证同一时刻只有一个线程能够进行写操作


ConcurrentLinkedQueue

ConcurrentLinkedQueue 是基于链表实现的线程安全队列,采用 CAS 算法实现无锁的并发访问。比如多个线程同时进行出队操作时,每个线程都可以独立地尝试更新头节点的引用,通过 CAS 操作确保只有一个线程能够成功更新,从而实现了无锁的并发访问


BlockingQueue

BlockingQueue 是阻塞队列,内部使用锁机制实现线程安全。当队列已满时,尝试向队列中添加元素的线程会被阻塞,直到队列有空间可用。当队列为空时,尝试从队列中获取元素的线程会被阻塞,直到队列有元素可获取

具体参考:https://www.cnblogs.com/Yee-Q/p/14580034.html


同步包装器

Java 同步包装器是指通过 Collections 类的静态方法将非线程安全的集合转换为线程安全的集合,主要包括以下几种:

  • synchronizedList:把普通的 List 转换为线程安全的列表。通过对 List 的所有操作添加同步锁,确保同一时刻只有一个线程能够访问列表,避免并发访问时出现数据不一致等问题,如 List synchronizedList = Collections.synchronizedList(new ArrayList());
  • synchronizedMap:把普通的 Map 转换为线程安全的映射。在对 Map 进行操作时,都会进行同步处理,保证多线程环境下 Map 的操作安全,如:Map synchronizedMap = Collections.synchronizedMap(new HashMap());
  • synchronizedSet:把普通的 Set 转换为线程安全的集合。通过同步机制,确保在多线程访问 Set 时,元素的添加、删除等操作不会出现并发问题,如:Set synchronizedSet = Collections.synchronizedSet(new HashSet());

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认