当前位置: 首页>后端>正文

Java的线程安全集合

Vector

单个方法的原子性(注:原子性,程序的原子性即不会被线程调度机制打断),并不能保证复合操作也具有原子性。

  • 虽然源代码注释里面说这个是线程安全的,因为确实很多方法都加上了同步关键字 synchronized ,但是对于复合操作而言,只是同步方法并没有解决线程安全的问题。要真正达成线程安全,还需要以 Vector 对象为锁,来进行同步处理。
  • 复合操作:
    Vector 所谓的线程安全是指调用 Vector 类的成员方法时,其他线程不能再访问该Vector对象。但是在调用两个Vector成员方法时,当前线程有可能再完成第一个方法后时间片到期,这时其他线程可以访问该Vector对象,造成调用第二个成员方法的结果可能与预想结果不同。这时为了保证线程安全,需要加synchronized。

Hashtable

Hashtable的get()方法和set()方法都加了同步锁进行控制

public synchronized V put(K key, V value) 
public synchronized V get(Object key)

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

ConcurrentHashMap

CAS + 局部(synchronized)锁定分段式锁 + Volatile
ConcurrentHashMap是弱一致性,仅在set方法进行同步锁控制,其get方法没有上锁,会导致get元素的并不是当前并行还未执行完的put的值,读取到的数据并不一定是最终的值,在一些要求强一致性的场景下可能会出错。例如:需要判断当前值是否为A如果不为A则修改为C,但是当前值为B而有个put方法将其更新为A还没执行完,则最终改值就是A,可能会造成后续程序或业务的异常。

CopyOnWriteArrayList

CopyOnWriteArrayList 和 ArrayList 都是基于数组的动态数组,封装了操作数组时的搬运和扩容等逻辑。
CopyOnWriteArrayList 还是用了基于加锁的 “读写分离” 和 “写时复制” 的方案解决线程安全问题:

  • 思想 1 - 读写分离(Read/Write Splitting): 将对资源的读取和写入操作分离,使得读取和写入没有依赖,在 “读多写少” 的场景中能有效减少资源竞争;
  • 思想 2 - 写时复制(CopyOnWrite,COW): 在写入数据时,不直接在原数据上修改,而是复制一份新数据后写入到新数据,最后再替换到原数据的引用上。这个特性各有优缺点:
    • 优点 1 - 延迟处理: 在没有写入操作时不会复制 / 分配资源,能够避免瞬时的资源消耗。例如操作系统的 fork 操作也是一种写时复制的思想;
    • 优点 2 - 降低锁颗粒度: 在写的过程中,读操作不会被影响,读操作也不需要加锁,锁的颗粒度从整个列表降低为写操作;
    • 缺点 1 - 弱数据一致性: 在读的过程中,如果数据被其他线程修改,是无法实时感知到最新的数据变化的;
    • 缺点 2 - 有内存压力: 在写操作中需要复制原数组,在复制的过程中内存会同时存在两个数组对象(只是引用,数组元素的对象还是只有一份),会带来内存占用和垃圾回收的压力。如果是 “写多读少” 的场景,就不适合。

CopyOnWriteArraySet:注意不是CopyOnWriteHashSet

他是线程安全的set,底层new了一个CopyOnWriteArrayList;然后使用的是volatile修饰的Object数组;
添加的时候调用addIfAbsent方法,会先遍历一次数组,如果存在则返回false;如果不存在添加到数组,并且采用ReentrantLock来保证线程安全;


https://www.xamrdz.com/backend/3fs1937703.html

相关文章: