【JavaEE初阶】 线程安全的集合类

简介: 【JavaEE初阶】 线程安全的集合类


🍀前言

原来的集合类, 大部分都不是线程安全的.

Vector, Stack, HashTable, 是线程安全的(不建议用), 其他的集合类不是线程安全的.

为什么不建议使用呢?

因为我们在使用的时候,这些类就会自动的加锁,虽然编译器会自动优化为没有锁竞争的线程进行锁消除的优化,但是呢万一编译器没有优化好,就麻烦了

所以我们更多的是搭配一下其他方案达到线程安全,比如以下几种

🌲多线程环境使用 ArrayList

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

这里博主就不做过多赘述了,不懂得宝子可以去看一下博主对着方面的讲解

🚩Collections.synchronizedList(new ArrayList);

  • synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的List.
  • synchronizedList 的关键操作上都带有 synchronized

🚩使用 CopyOnWriteArrayList

使用CopyOnWrite容器即写时复制的容器

  • 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,
  • 添加完元素之后,再将原容器的引用指向新的容器

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。

所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

优点:

在读多写少的场景下, 性能很高, 不需要加锁竞争.

缺点:

  1. 占用内存较多.
  2. 新写的数据不能被第一时间读取到

🎍多线程环境使用队列

这个博主在讲【JavaEE初阶】 阻塞式队列详解中也有提到,所以这里只是简单列举

  1. ArrayBlockingQueue
    基于数组实现的阻塞队列
  2. LinkedBlockingQueue
    基于链表实现的阻塞队列
  3. PriorityBlockingQueue
    基于堆实现的带优先级的阻塞队列
  4. TransferQueue
    最多只包含一个元素的阻塞队列

🎋多线程环境使用哈希表

HashMap 本身不是线程安全的.

在多线程环境下使用哈希表可以使用:

  • Hashtable
  • ConcurrentHashMap

🚩Hashtable

Hashtable在实现过程中只是简单的把关键方法加上了 synchronized 关键字.

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

  • 如果多线程访问同一个 Hashtable 就会直接造成锁冲突.
  • size 属性也是通过 synchronized 来控制同步, 也是比较慢的.
  • 一旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到大量的元素拷贝, 效率会非常低

🚩ConcurrentHashMap

相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例

  • 读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁. 加锁的方式仍然是是用 synchronized, 但是不是锁整个对象, 而是 “锁桶” (用每个链表的头结点作为锁对象), 大大降低了锁冲突的概率.
  • 充分利用 CAS 特性. 比如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.

优化了扩容方式: 化整为零

  • 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去.
  • 扩容期间, 新老数组同时存在.
  • 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运一小部分元素.
  • 搬完最后一个元素再把老数组删掉.
  • 这个期间, 插入只往新数组加.
  • 这个期间, 查找需要同时查新数组和老数组

    想要详细了解的读者可以去ConcurrentHashMap源码分析(JDK8版本)进行查看

🌳相关面试题

  1. ConcurrentHashMap的读是否要加锁,为什么?

读操作没有加锁. 目的是为了进一步降低锁冲突的概率. 为了保证读到刚修改的数据, 搭配了 volatile 关键字.

  1. 介绍下 ConcurrentHashMap的锁分段技术?

这个是 Java1.7 中采取的技术. Java1.8 中已经不再使用了. 简单的说就是把若干个哈希桶分成一个 “段” (Segment),针对每个段分别加锁. 目的也是为了降低锁竞争的概率. 当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争.

  1. ConcurrentHashMap在jdk1.8做了哪些优化?

取消了分段锁, 直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对 象). 将原来 数组 + 链表的实现方式改进成 数组 + 链表 / 红黑树 的方式. 当链表较长的时候(大于等于 8 个元素)就转换成红黑树.

  1. Hashtable和HashMap、ConcurrentHashMap 之间的区别?
  • HashMap: 线程不安全. key 允许为 null
  • Hashtable: 线程安全. 使用 synchronized 锁,Hashtable 对象, 效率较低. key 不允许为 null.
  • ConcurrentHashMap: 线程安全. 使用synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用 CAS 机制. 优化了扩容方式. key 不允许为 null

⭕总结

关于《【JavaEE初阶】 线程安全的集合类》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
5天前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
11 1
|
6天前
|
Java
Java中,有两种主要的方式来创建和管理线程:`Thread`类和`Runnable`接口。
【6月更文挑战第24天】Java创建线程有两种方式:`Thread`类和`Runnable`接口。`Thread`直接继承受限于单继承,适合简单情况;`Runnable`实现接口可多继承,利于资源共享和任务复用。推荐使用`Runnable`以提高灵活性。启动线程需调用`start()`,`Thread`直接启动,`Runnable`需通过`Thread`实例启动。根据项目需求选择适当方式。
18 2
|
11天前
|
Java C++ 开发者
线程创建的终极对决:Thread 类 VS Runnable 接口,你站哪边?
【6月更文挑战第19天】在Java多线程编程中,通过`Thread`类直接继承或实现`Runnable`接口创建线程各有优劣。`Thread`方式简洁但不灵活,受限于Java单继承;`Runnable`更灵活,适合资源共享和多接口实现,提高代码可维护性。选择取决于项目需求和设计原则,需权衡利弊。
|
6天前
|
API C++
c++进阶篇——初窥多线程(三)cpp中的线程类
C++11引入了`std::thread`,提供对并发编程的支持,简化多线程创建并增强可移植性。`std::thread`的构造函数包括默认构造、移动构造及模板构造(支持函数、lambda和对象)。`thread::get_id()`获取线程ID,`join()`确保线程执行完成,`detach()`使线程独立,`joinable()`检查线程状态,`operator=`仅支持移动赋值。`thread::hardware_concurrency()`返回CPU核心数,可用于高效线程分配。
|
24天前
|
Java Apache Spring
面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
【6月更文挑战第3天】面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
14 0
|
26天前
|
安全 Java 容器
多线程(进阶四:线程安全的集合类)
多线程(进阶四:线程安全的集合类)
18 0
|
3天前
|
存储 测试技术
【工作实践(多线程)】十个线程任务生成720w测试数据对系统进行性能测试
【工作实践(多线程)】十个线程任务生成720w测试数据对系统进行性能测试
10 0
【工作实践(多线程)】十个线程任务生成720w测试数据对系统进行性能测试
|
4天前
|
数据采集 Java Unix
10-多线程、多进程和线程池编程(2)
10-多线程、多进程和线程池编程
|
4天前
|
安全 Java 调度
10-多线程、多进程和线程池编程(1)
10-多线程、多进程和线程池编程
|
9天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。