【Java EE】线程安全的集合类

目录

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

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

那如何在多线程环境下安全的使用 集合类呢?

🌴多线程环境使用 ArrayList

  1. ⾃⼰使⽤同步机制 (synchronized 或者 ReentrantLock)

前⾯做过很多相关的讨论了. 此处不再展开.可以查看博主之前的博客
2. Collections.synchronizedList(new ArrayList);

synchronizedList 是标准库提供的⼀个基于 synchronized 进⾏线程同步的 List. synchronizedList

的关键操作上都带有 synchronized

  1. 使⽤ CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器。

• 当我们往⼀个容器添加元素的时候,不直接往当前容器添加,⽽是先将当前容器进⾏Copy,复制

出⼀个新的容器,然后新的容器⾥添加元素,

• 添加完元素之后,再将原容器的引⽤指向新的容器。

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

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

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

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

🎍多线程环境使⽤队列

  1. ArrayBlockingQueue

基于数组实现的阻塞队列

  1. LinkedBlockingQueue

基于链表实现的阻塞队列

  1. PriorityBlockingQueue

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

  1. TransferQueue

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

🍀多线程环境使⽤哈希表

HashMap 本⾝不是线程安全的.

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

• Hashtable

•ConcurrentHashMap

🌸 Hashtable

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

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

• 如果多线程访问同⼀个 Hashtable 就会直接造成锁冲突.

• size 属性也是通过 synchronized 来控制同步, 也是⽐较慢的.

• ⼀旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到⼤量的元素拷⻉, 效率会⾮常低.

🌸ConcurrentHashMap

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

  1. 读操作没有加锁(但是使⽤了 volatile 保证从内存读取结果), 只对写操作进⾏加锁. 加锁的⽅式仍然是⽤ synchronized, 但是不是锁整个对象, ⽽是 "锁桶" (⽤每个链表的头结点作为锁对象), ⼤⼤降低了锁冲突的概率.
  2. 充分利⽤ CAS 特性. ⽐如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.
  3. 优化了扩容⽅式: 化整为零
  • 发现需要扩容的线程, 只需要创建⼀个新的数组, 同时只搬⼏个元素过去.
  • 扩容期间, 新⽼数组同时存在.
  • 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运⼀⼩部
    分元素.
  • 搬完最后⼀个元素再把⽼数组删掉.
  • 这个期间, 插⼊只往新数组加.
  • 这个期间, 查找需要同时查新数组和⽼数组

    ConcurrentHashMap每个哈希桶都有一把锁,只有两个线程访问的恰好是同一个哈希桶上的数据才会出现锁冲突。

⭕相关面试题

  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

🔥其他常⻅问题

  1. 谈谈 volatile关键字的⽤法?

volatile 能够保证内存可⻅性. 强制从主内存中读取数据. 此时如果有其他线程修改被 volatile 修饰的

变量, 可以第⼀时间读取到最新的值

  1. Java多线程是如何实现数据共享的?

JVM 把内存分成了这⼏个区域:

⽅法区, 堆区, 栈区, 程序计数器.

其中堆区这个内存区域是多个线程之间共享的.

只要把某个数据放到堆内存中, 就可以让多个线程都能访问到.

  1. Java创建线程池的接⼝是什么?参数 LinkedBlockingQueue 的作⽤是什么?

创建线程池主要有两种⽅式:

• 通过 Executors ⼯⼚类创建. 创建⽅式⽐较简单, 但是定制能⼒有限.

• 通过 ThreadPoolExecutor 创建. 创建⽅式⽐较复杂, 但是定制能⼒强.

LinkedBlockingQueue 表⽰线程池的任务队列. ⽤⼾通过 submit / execute 向这个任务队列中

添加任务, 再由线程池中的⼯作线程来执⾏任务.

  1. Java线程共有⼏种状态?状态之间怎么切换的?

• NEW: 安排了⼯作, 还未开始⾏动. 新创建的线程, 还没有调⽤ start ⽅法时处在这个状态.

• RUNNABLE: 可⼯作的. ⼜可以分成正在⼯作中和即将开始⼯作. 调⽤ start ⽅法之后, 并正在 CPU 上

运⾏/在即将准备运⾏ 的状态.

• BLOCKED: 使⽤ synchronized 的时候, 如果锁被其他线程占⽤, 就会阻塞等待, 从⽽进⼊该状态.

• WAITING: 调⽤ wait ⽅法会进⼊该状态.

• TIMED_WAITING: 调⽤ sleep ⽅法或者 wait(超时时间) 会进⼊该状态.

• TERMINATED: ⼯作完成了. 当线程 run ⽅法执⾏完毕后, 会处于这个状态

  1. 在多线程下,如果对⼀个数进⾏叠加,该怎么做?

• 使⽤ synchronized / ReentrantLock 加锁

• 使⽤ AtomInteger 原⼦操作.

  1. Servlet是否是线程安全的?

Servlet 本⾝是⼯作在多线程环境下.

如果在 Servlet 中创建了某个成员变量, 此时如果有多个请求到达服务器, 服务器就会多线程进⾏操

作, 是可能出现线程不安全的情况的

  1. Thread和Runnable的区别和联系?

Thread 类描述了⼀个线程.

Runnable 描述了⼀个任务.

在创建线程的时候需要指定线程完成的任务, 可以直接重写 Thread 的 run ⽅法, 也可以使⽤

Runnable 来描述这个任务.

  1. 多次start⼀个线程会怎么样

第⼀次调⽤ start 可以成功调⽤.

后续再调⽤ start 会抛出 java.lang.IllegalThreadStateException 异常

  1. 有synchronized两个⽅法,两个线程分别同时⽤这个⽅法,请问会发⽣什么?

synchronized 加在⾮静态⽅法上, 相当于针对当前对象加锁.

如果这两个⽅法属于同⼀个实例:

线程1 能够获取到锁, 并执⾏⽅法. 线程2 会阻塞等待, 直到线程1 执⾏完毕, 释放锁, 线程2 获取到锁之

后才能执⾏⽅法内容.

如果这两个⽅法属于不同实例:

两者能并发执⾏, 互不⼲扰.

  1. 进程和线程的区别?

• 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。

• 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.

• 进程是系统分配资源的最⼩单位,线程是系统调度的最⼩单位。

相关推荐
羊锦磊2 小时前
[ Mybatis 多表关联查询 ] resultMap
java·开发语言·数据库·mysql·mybatis
ZeroToOneDev4 小时前
Java(泛型和JUnit)
java·开发语言·笔记
迪尔~6 小时前
Apache POI中通过WorkBook写入图片后出现导出PDF文件时在不同页重复写入该图片问题,如何在通过sheet获取绘图对象清除该图片
java·pdf·excel
现在,此刻6 小时前
leetcode 11. 盛最多水的容器 -java
java·算法·leetcode
DKPT7 小时前
Java设计模式之开闭原则介绍与说明
java·设计模式·开闭原则
hyy27952276847 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
布朗克1687 小时前
Spring Boot项目通过Feign调用三方接口的详细教程
java·spring boot·feign
Arva .7 小时前
Spring基于XML的自动装配
xml·java·spring
帅得不敢出门9 小时前
Android Framework定制长按电源键关机的窗口
android·java·framework
fatfishccc9 小时前
循序渐进学 Spring (上):从 IoC/DI 核心原理到 XML 配置实战
xml·java·数据库·spring·intellij-idea·ioc·di