线程安全的集合类

简介: 线程安全的集合类

Java中提供了许多集合类,其中有的是线程安全的,有的是线程不安全的。线程安全的集合类有:

1. Vector:Vector类实现了一个动态数组,与ArrayList相似,但Vector是同步访问的

2. Stack:Stack是Vector的一个子类,实现了一个“后进先出”的栈

3. Hashtable:Hashtable是一个散列表,与HashMap类似

Hashtable在关键方法上加上了synchronized,相当于对this加锁(整张表都加上锁),则Hashtable只有一把锁,即使是修改或读取不同链表上的元素,也会触发锁冲突

通过上图我们可以发现:读取数据时不涉及到线程安全问题,修改两个不同链表上的元素时,也不会涉及线程安全问题,而当修改的是同一链表上的元素时,则可能会涉及到线程安全问题。

因此,针对读取操作,无需加锁,不同链表的操作,也无需加锁,而当针对同一链表操作时,需要加锁,此时,我们可以考虑使用ConcurrentHashMap

4. ConcurrentHashMap:ConcurrentHashMap能够做到读数据不加锁,且在进行写操作时锁的粒度更小,可以允许多个修改操作并发进行,Java 1.7及其之前,ConcurrentHashMap是通过“分段锁”来实现的,即给若干链表分配一把锁,然而这种方法需要引入额外的空间开销,且实现更复杂

因此,从Java 8 开始,就变成了每个链表一把锁了

此时,若不是操作同一个链表的锁,就不会发生锁冲突

然而此种方法是否需要更多的空间?

不会产生更多的空间代价。Java中任意一个对象都可以直接作为锁对象。哈希表中本就需要有数组,数组的元素都是已经存在的(每个链表的头节点),此时,只需使用链表的头节点(数组元素)作为锁对象即可

且ConcurrentHashMap针对扩容也进行了优化。

Hashtable一旦触发扩容,就由该线程完成整个扩容操作,此时可能会涉及到大量的元素拷贝,因此效率会较低,耗时较长

而ConcurrentHashMap则是采用“化整为零”的方法,即当发现需要扩容时,创建一个新的数组,同时搬运几个元素过去,后续每次线程操作ConcurrentHashMap时,都会搬运元素,每次操作搬运一部分元素。在此扩容期间,新旧数组同时存在。当搬运完最后一个元素时,再把旧数组删掉。在此期间内,插入数据只会向新数组中插入,而查找需要同时查询新数组和旧数组

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

6. CopyOnWriteArrayList:CopyOnWrite即写时拷贝,当读取顺序表时,此时无线程安全问题,而当有线程要修改其中的值时,就会将list复制一份,修改新表中的内容,并修改引用的指向(操作是原子的,无需加锁)

然而因此此种操作需要进行复制,因此修改操作不能太频繁且表也不应太大

除此之外,也可以根据需求,使用synchronized或ReentrantLock自己实现线程安全的ArrayList

7. BlockingQueue(阻塞队列)

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

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

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

...

虽然以上这些类都是线程安全的,但也不一定能够满足我们多线程操作的需求,因此我们需要根据实际情况选择使用或自己加以实现

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