线程安全的集合类(多线程环境下使用ArrayList、队列及哈希表)

简介: 线程安全的集合类(多线程环境下使用ArrayList、队列及哈希表)

多线程环境下使用ArrayList

在多线程环境下使用ArrayList可以有以下三种方式:

1.使用同步机制 (synchronized 或者 ReentrantLock)

2.Collections.synchronizedList(new ArrayList)synchronizedList 是标准库提供的一个基于synchronized 进行线程同步的 List,synchronizedList 的关键操作上都带有 synchronized,使用这个方法把集合类套一层。

3.使用 CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器,简称“COW”,也叫做“写时拷贝”。如果针对这个ArrayList进行读操作,不做任何额外的工作。如果进行写操作,则拷贝一份新的ArrayList,针对新的进行修改,修改过程中如果有读操作,就继续读旧的这份数据。当修改完毕了,使用新的替换旧的(本质上就是一个引用之间的赋值,是原子的)。

很明显,这种方案,优点是不需要加锁,不需要锁竞争,在读多写少的场景下的性能很高;缺点则是要求这个ArrayList不能太大,否则占用内存太大,而且新写的数据不能第一时间被读到。


多线程环境下使用队列

1) ArrayBlockingQueue 基于数组实现的阻塞队列

2) LinkedBlockingQueue 基于链表实现的阻塞队列

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

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

TransferQueue 的应用场景是,当不想生产者过度生产消息时,TransferQueue可能非常有用,在这样的设计中,消费者的消费能力将决定生产者产生消息的速度。


多线程环境下使用哈希表

HashMap 本身不是线程安全的,在多线程环境下使用哈希表可以使用:Hashtable和ConcurrentHashMap

Hashtable是线程安全的,它是在关键方法上加了synchronized。更推荐使用的是ConcurrentHashMap,它是更优化的线程安全哈希表。

ConcurrentHashMap进行了哪些优化?比HashTable好在哪里?和HashTable之间的区别是啥?

1.最大的优化之处:ConcurrentHashMap相比于HashTable 大大缩小了锁冲突的概率,把一把大锁,转换成多把小锁了。


HashTable的做法是直接在方法上加synchronized,等于是给this加锁,只要操作哈希表上的任意元素,都会产生加锁,也就都可能会发生锁冲突。但是实际上,基于哈希表的结构特点,有些元素在进行并发操作的时候,是不会产生线程安全问题的,也就不需要使用锁控制。

1dd8eed7f9bf4039bbcb1156c8d86887.png

此时元素1和元素2在同一链表上,如果线程A修改(增删)元素1,线程B修改元素2,那么此时是有线程安全问题的(相邻两元素并发的插入或者删除的时候,相邻两节点的next指向可能会发生改变)。如果线程A修改(增或者删)元素3,线程B修改元素4,,这个情况相当于多个线程修改不同的变量,那么此时是没有线程安全问题的。


使用HashTable,锁冲突概率就太大了,任何两个元素的操作都会有锁冲突,即使是处在不同的链表上,这就是不用HashTable的主要原因。


ConcurrentHashMap做法是,每个链表有各自的锁(而不是大家共用同一个锁了),具体来说,就是使用每个链表的头结点,作为锁对象(两个线程针对同一个锁对象加锁才有锁竞争,才有阻塞等待,针对不同对象,没有锁竞争)。

此时,把锁的粒度变小了,针对12这个情况,是针对同一把锁进行加锁,会有锁竞争,会保证线程安全。针对34这个情况,是针对不同的锁进行加锁,不会有锁竞争了,没有阻塞等待,程序就会更快。


2.针对读操作,不加锁,只针对写操作加锁

读和读之间没有冲突;写和写之间有冲突,可以加锁;读和写之间没有冲突,但是很多场景下,读写之间不加锁控制,如果写操作不是原子的,那么会产生脏读,所以使用了 volatile 保证了原子性。


3.ConcurrentHashMap内部充分的使用了CAS,通过这个也来进一步的削减加锁操作的数目


4.针对扩容,采取了"化整为零"的方式

HashMap/HashTable扩容:

创建一个更大的数组空间,把旧的数组上的链表上的每个元素搬运到新的数组上(删除+插入),这个扩容操作会在某次put 的时候进行触发。如果元素个数特别多,就会导致这样的搬运操作,比较耗时。


ConcurrentHashMap 中,扩容采取的是每次搬运一小部分元素的方式。创建新的数组,旧的数组也保留。每次 put 操作,都往新数组上添加,同时进行一部分搬运(把一小部分旧的元素搬运到新数组上)。每次get的时候,则旧数组和新数组都查询。每次remove 的时候,只是把元素删了就行了。经过一定时间之后,所有的元素都搬运好了,最终再释放旧数组。

小结:

Hashtable和HashMap、ConcurrentHashMap 之间的区别?

HashMap: 线程不安全. key 允许为 null

Hashtable: 线程安全,使用 synchronized 锁 Hashtable 对象, 效率较低,key 不允许为 null

ConcurrentHashMap: 线程安全,使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用

CAS 机制,优化了扩容方式,key 不允许为 null

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

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

8 个元素)就转换成红黑树。

PS:分段锁是 Java1.7 中采取的技术,Java1.8 中已经不再使用了,简单的说就是把若干个哈希桶分成一个

"段", 针对每个段分别加锁,目的也是为了降低锁竞争的概率。当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争。

ConcurrentHashMap的读是否要加锁?

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

volatile 关键字。


相关文章
|
14天前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
29 1
|
14天前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
25 1
|
14天前
|
设计模式 SQL 安全
Java面试题:设计一个线程安全的内存管理器,使用观察者模式来通知所有线程内存使用情况的变化。如何确保在添加和移除内存块时的线程安全?如何确保任务的顺序执行和调度器的线程安全?
Java面试题:设计一个线程安全的内存管理器,使用观察者模式来通知所有线程内存使用情况的变化。如何确保在添加和移除内存块时的线程安全?如何确保任务的顺序执行和调度器的线程安全?
13 0
|
14天前
|
存储 设计模式 监控
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
21 0
|
14天前
|
设计模式 安全 NoSQL
Java面试题:结合单例模式与Java内存管理,设计一个线程安全的单例类?分析Java多线程工具类ExecutorService与Java并发工具包中的工具类,设计一个Java并发框架的分布式锁实现
Java面试题:结合单例模式与Java内存管理,设计一个线程安全的单例类?分析Java多线程工具类ExecutorService与Java并发工具包中的工具类,设计一个Java并发框架的分布式锁实现
18 0
|
16天前
|
Java
实现Java多线程中的线程间通信
实现Java多线程中的线程间通信
|
17天前
|
Java
实现Java多线程中的线程间通信
实现Java多线程中的线程间通信
|
12天前
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
26 0
|
12天前
|
存储 Linux 调度
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
32 0
|
14天前
|
设计模式 并行计算 安全
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
16 0