线程安全的集合类

简介: 线程安全的集合类

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

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

多线程使用ArrayList

1.自己使用同步机制(Synchronized或者ReentrantLock),前面已经做过许多讨论了,这里不再展开.

2.Collections.synchronizedList(new ArrayList);

相当于给ArrayList套壳->得到了新的对象,新对象里面的方法都是加锁的.

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

3.使用CopyOnWriteArrayList.

CopyOnWrite容器即写时复制的容器,这个主要用于解决多个线程修改一个数据的问题.

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

添加完元素之后,再将原容器的引用指向新容器.

一旦有线程修改值时,把顺序表复制一份,修改这个顺序表的内容,并修改引用的指向(这个操作是原子的)

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

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

比如服务器在加载配置文件时,就需要把配置文件解析出来放到内存数据结构.

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

缺点:1.占用内存较多(因此修改不能太频繁). 2.新写的数据不能被第一时间读取到.

多线程环境使用队列

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

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

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

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

多线程环境使用哈希表

HashMap本身是不安全的.

在多线程环境下可以使用: HashTable, ConcurrentHashMap.

HashTable

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

public synchronized V put(K key, V value) {

public synchronized V get(Object key) {

这相当于直接针对HashTable对象加锁.

如果多线程访问同一个HashTable就会造成锁冲突.

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

一旦触发扩容,就由该线程完成整个扩容过程,这个过程就会涉及到大量的元素拷贝,效率非常低.

一个HashTable只有一把锁.两个线程访问HashTable中的任意数据都会出现锁竞争.

我们认为这样的类仍然不好用.我们希望:如果修改两个不同链表上的元素,不涉及线程安全的问题(因为修改的是不同变量).但如果在同一个链表上修改,涉及线程安全问题.操作引用时,就可能涉及到操作同一引用.因此我们在这里引入ConcurrentHashMap.

ConcurrentHashMap

相比于HashTable做出了一系列的改进和优化.它就解决了HashTable出现的部分问题:即针对同一个链表操作再加锁,针对不同链表操作,不必加锁(不要产生锁冲突)->缩小了锁的粒度

下面来看一下详细介绍:

读操作没有加锁(但是使用volatile保证从内存中读取结果),只对写操作进行加锁.加锁的方式仍然是使用synchronized,但是不是锁整个对象,而是"锁桶"(用每个链表的头结点作为锁对象),大大降低了锁冲突的概率.

充分利用了CAS的特性.比如size属性通过CAS来更新.避免出现重量级锁的情况.

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

   (1)发现需要扩容的线程,只需要创建一个新的数组,同时搬几个元素过去.

   (2)扩容期间,新老数组同时存在

   (3)后续每个来操作ConcurrentHashMap的线程,都会参与搬家的过程.每个操作负责搬运一小部分元素

   (4)搬完最后一个元素再把老数组删掉

   (5)这个期间,插入只往新数组中加.

   (6)这个期间,查找需要同时查新数组和老数组.

这种设定,不会产生更多的空间代价.因为java中任何一个对象都可以直接作为锁对象.本身,在哈希表中,就得有数组,数组元素都是存在的(每个链表的头节点),用其做对象加锁即可.

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

相关面试题

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

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

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

这个是Java1.7所采取的技术.Java1.8中已经不再使用了.简单的说就是把若干个哈希桶分成一个"段"(Segment),针对每个段分别加锁.

目的也是为了降低锁冲突的概率.当两个线程访问的数据恰好在同一个段上时,才会触发锁竞争

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

取消了分段锁,直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头节点对象作为锁对象).

将原来的数组 + 链表的实现方式改进成 数组 + 链表 /红黑树的方式.当链表较长的时候(大于等于8个元素)就转换成红黑树.

4.HashMap和HashTable,ConcurrentHashMap之间的区别?

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

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

ConcurrentHashMap: 线程安全.使用synchronized锁每个链表的头节点,锁冲突概率较低,充分利用CAS机制,优化了扩容方式.key不允许为null.

相关文章
|
4天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
Lua的面向对象编程、协同线程与协同函数的概念和使用,以及Lua文件I/O操作的基本方法。
27 4
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
|
20天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
15 3
|
20天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
29 2
|
20天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2
|
20天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
27 1
|
1月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
33 0
|
3月前
|
安全 Java 调度
|
3月前
|
安全 Java 程序员
线程安全与 Vector 类的分析
【8月更文挑战第22天】
48 4
|
3月前
|
安全 Java API
Java多线程编程:使用Atomic类实现原子操作
在Java多线程环境中,共享资源的并发访问可能导致数据不一致。传统的同步机制如`synchronized`关键字或显式锁虽能保障数据一致性,但在高并发场景下可能导致线程阻塞和性能下降。为此,Java提供了`java.util.concurrent.atomic`包下的原子类,利用底层硬件的原子操作确保变量更新的原子性,实现无锁线程安全。
30 0