首页 > Java 集合类不安全及解决方法
头像
Mkbk
发布于 2020-12-25 19:24
+ 关注

Java 集合类不安全及解决方法

Java 集合类不安全及解决方法

一、集合类为什么线程不安全?

先演示一下集合类线程不安全的情况:
多个线程向一个 ArrayList 中插入元素。

public class NotSafeDemo {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

运行会报 java.util.ConcurrentModificationException(并发修改异常) :
图片说明

为什么会这样?因为 ArrayList 线程不安全。
那为什么 ArrayList 线程不安全?因为它的 add 方法没有加锁,多个线程并发过来add,就可能会出现异常。
可以看一下源码:
图片说明

二、解决集合类不安全的方法 1 —— Vector

看一下ArrayList 和 Vector 的区别:

图片说明
Vector 是 List 接口的古老实现类,ArrayList 是 List 接口后面新增的实现类。除了线程安全问题与扩容方式不同,Vector 几乎与 ArrayList 一样。

所以,可以把 Vector 作为解决 ArrayList 线程安全的一种方式(不过 Vector 效率太低)。

实现:

public class NotSafeDemo {
    public static void main(String[] args) {

        List<String> list = new Vector<>();
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                //向 list 中插入随机字符串
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

运行不会报错了。

其实 Vector 之所以在多线程下插入元素不会发生问题,是因为 Vector 的 add 方法加了锁。
同样可以看下源码:
图片说明
(顺便说一下,其实 Vector 读方法也加了锁,相当于读的时候,同一时刻也只能有一个线程能读。)

三、解决集合类不安全的方法 2 —— Collections

Collections 是 Collection 的工具类,其中就提供了一个方法,可以将线程不安全的 ArrayList 转换成线程安全的。

看下实现:

public class NotSafeDemo {
    public static void main(String[] args) {

        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                //向 list 中插入随机字符串
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

至于怎么将 ArrayList 的 add 方法转换成安全的,同样,也是看下源码就知道了:
图片说明
懂了吧?

并且 Collections 工具类也支持将 HashMap, HashSet 等转换成安全的。
图片说明

四、解决集合类不安全的方法 3 —— CopyOnWriteArrayList(写时复制)

这也是今天的主角: JUC 中的 CopyOnWriteArrayList

先看一下实现:

public class NotSafeDemo {
    public static void main(String[] args) {

        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                //向 list 中插入随机字符串
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

这个相比于前面那些,效率又好,读的又快,又能保证一致性。

CopyOnWriteArrayList 的思想是 写时复制

写时复制:我们要向一个文件中添加新数据时,先将原来文件拷贝一份,然后在这个拷贝文件上进行添加;而此时如果有别人读取数据,还是从原文件读取;添加数据完成后,再用这个拷贝文件替换掉原来的文件。这样做的好处是,读写分离,写的是拷贝文件,读的是原文件,可以支持多线程并发读取,而不需要加锁。

再来看一眼源码:

其中的 setArray 方法中的 array 是用 volatile 修饰的,可以保证可见性:
图片说明

同样,JUC 也有 HashMap, HashSet 对应线程安全的实现:
HashSet => CopyOnWriteArraySet
HashMap => ConcurrentHashMap

怎么样,大家学会了没有?

全部评论

(2) 回帖
加载中...
话题 回帖

相关热帖

近期精华帖

热门推荐