当前位置:   article > 正文

线程安全的集合类有哪些?_线程安全的集合有哪些

线程安全的集合有哪些

验证ArrayList线程不安全
ArrayList 应当是开发中用到的最多的集合类,是动态列表,List 接口的实现类。

多数情况下,我们实在单线程环境使用,或者是在方法内部,以局部变量的形式使用,一般不会出现线程安全问题。

但是当ArrayList置身于多线程环境时,很容易因为自身的fail-fast 机制抛出异常 ConcurrentModificationException 。

比如下面的代码

/**
 * 验证ArrayList的线程不安全,并提供几种线程安全的列表的解决方案
 *
 * @author linjinjia linjinjia047@163.com
 * @date 2021/3/19 22:38
 */
public class ListThreadSafeDemo {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            final int j = i;
            new Thread(() -> {
                list.add(j);
                System.out.println(list);
            }, "" + i).start();
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

输出(输出结果不是唯一的,也有可能不抛出异常):
在这里插入图片描述

ConcurrentModificationException 可以作为一种检测bug的方式,但是不能在程序中依赖此异常。

线程安全的集合哪里找?

在众多的List 接口实现类中,总有一部分是为了线程安全而设计的。

使用 Collections.synchronizedList(List list) 方法
该方法像是一个包装操作,将传入的 list 进行包装,调用 list 的方法之前,进行同步处理。

返回列表对象的类,都是继承了一个 SynchronizedCollection 类,该类有一个成员变量 Object mutex,用来做同步处理时使用。
在这里插入图片描述
在调用List 的方法时,会先对 mutex 进行同步,然后再调用 c 对应的方法。追踪 synchronizedList 方法的源码,会很容易发现这一点。

Collections.synchronizedList(List list) 方法的注释中,指出了遍历返回列表时,建议手动进行同步,并给了个示例。

List list = Collections.synchronizedList(new ArrayList());
    ...
    synchronized (list) {
        Iterator i = list.iterator(); // Must be in synchronized block
        while (i.hasNext())
            foo(i.next());
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这是因为返回列表的 listIterator() 和 listIterator(int index) 方法都是直接返回 c 的迭代器,所以遍历需要自己进行同步。

使用Vector类
Vector 是 jdk1.0 的古老集合类,该类对大部分方法都加上了 synchronized 关键字,用来保证线程安全。

该类的 listIterator 和 iterator 返回的迭代器是支持 fail-fast 的。

还有一个 elements() 方法,返回一个 Enumeration 对象,只有 hasMoreElements() 和 nextElement()方法,不支持 fail-fast。

Vector 和 synchronizedList 的区别是,一个是Vector对方法加锁,无法控制锁的粒度;二是Vector进行加锁的对象是 this 本身,无法控制锁的对象。

使用 CopyOnWriteArrayList
CopyOnWrite 也叫 COW。

CopyOnWrite 容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object添加。

而是先将当前容器 Object[] 进行复制,复制一个新的容器 Object[] newElement 并往其中里添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(new Element) 。

这样做的好处是可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite 容器也是一种读写分离的思想,读和写不同的容器。
在这里插入图片描述
在这里插入图片描述
可以看到 add 方法中,是先复制原来的数组,然后增加新的元素,最后再赋值回原来的数组,这个过程是加锁的。

CopyOnWriteArrayList 适合读多写少的场景,写多的情况下,频繁地加锁和复制,也是一笔很大的开销。

拓展
以上三种方式适用于 List ,但是 Set 和 Map 也有类似的方式,去实现线程安全。

比如 Collections 中也有 synchronizedSetsynchronizedMap 方法,其原理也跟 synchronizedList 一样。

HashMap 和 HashTable 的关系犹如ArrayList 和 Vector 。

java.util.concurrent 下也有 CopyOnWriteSet, ConcurrentHashMap 这种线程安全的集合。

而且, CopyOnWriteSet 底层依赖的是 CopyOnWriteArrayList ,比如它的 add 方法就是调用了 CopyOnWriteArrayList 的 addIfAbsent() 方法。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/670943
推荐阅读
相关标签
  

闽ICP备14008679号