前文
文章目录
为什么说 ArrayList 是线程不安全的?
看过我之前写的深入理解 ArrayList 的朋友们应该都知道 ArrayList 是线程安全的,但是为什么说 ArrayList 是线程不安全的呢?
我们先来看下下面这段代码,使用了 ArrayList 添加数据
package juc;
import java.util.ArrayList;
import java.util.List;
/**
* @author Woo_home
* @create by 2020/3/7
*/
public class NotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("a");
for (String s : list) {
System.out.println(s);
}
}
}
输出:
这段代码相信大家都能看得懂,就是添加数据然后遍历打印结果而已
但是我们这里讲的 ArrayList 是线程不安全的是什么意思呢?由于我们的 main 方法本身是一个线程,不懂的朋友请看这里 一文搞懂 Java 线程,但是这里涉及的线程安全问题肯定是多个线程同时操作的
OK,现在我们创建三个线程,而不是一个线程了,代码如下:
我们往 list 添加一个随机字符串,然后打印该结果
package juc;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author Woo_home
* @create by 2020/3/7
*/
public class NotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
OK ,我们打印一下:
我们发现这个打印的数值好像不对,我们再打印几次看一下
正确的打印应该是下面这幅图这样的
可以发现,当我们创建三个线程同时存储读取数据的时候会导致每个线程获取到的数据不确定,甚至还会抛出 ConcurrentModificationException 异常(并发修改异常)
故障现象
java.util.ConcurrentModificationException(并发修改异常)
List<String> list = new ArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
当我们运行这段代码的时候就会出现异常了
导致原因
很多朋友可能会说这是因为 ArrayList 的 add() 方法没有加锁
我们先来看下 ArrayList 的 add 源码,想看 add() 方法详细源码的朋友请看这里 深入理解 ArrayList
通过源码我们可以发现,ArrayList 的 add() 方法确实没有加锁(synchronized)
解决方法
第一种(使用 Vector)
我们来看下下面这个 UML 图
通过这个 UML 图可以知道,Vector 和 ArrayList 都实现了 List 接口,我们将代码中的 ArrayList 换成 Vector 试下
package juc;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
/**
* @author Woo_home
* @create by 2020/3/7
*/
public class NotSafeDemo {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
输出:
从输出结果来看我们的程序已经没有报错并且正常输出了,这是什么原因呢?为什么将 ArrayList 替换成 Vector 就没报错了呢?,我们来看下 Vector 的 add() 方法源码
通过源码我们可以发现 Vector 的 add() 方法是加了锁的(synchronized),但是 Vector 是从 JDK1.2 开始就已经有了的,我们能想到的那些大佬想不到吗?
但是,两全是不能其美的,你既要效率又要安全,对吧。Vector 虽然解决了数据的一致性,但是并发性却大大地降低了
第二种(使用 Collections 工具集里面的 synchronizedList 方法)
package juc;
import java.util.*;
/**
* @author Woo_home
* @create by 2020/3/7
*/
public class NotSafeDemo {
public static void main(String[] args) {
// 使用 Collections 工具集的 synchronizedList 方法
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
第三种(使用 CopyOnWriteArrayList)
CopyOnWriteArrayList 是 JUC 里面的一个类,这个类的中文意思是 —— 写时复制
而且 CopyOnWriteArrayList 这个类也是实现了 List 接口的,来看下 UML 图
代码实现
package juc;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Woo_home
* @create by 2020/3/7
*/
public class NotSafeDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
输出: