这里写目录标题
众所周知,List中的ArrayList是线程非安全的,在使用多线程操作ArrayList可能会出现ConcurrentModificationException(并发修改问题)。如图
package testJUC;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TestList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
//添加UUID随机字符串的截取索引1到5的字符串
list.add(UUID.randomUUID().toString().substring(1,5));
System.out.println(list);
},i+"").start();
}
}
}
那为什么会出现这种问题呢?原因是此类的iterator和listIterator方法返回的迭代器是快速失败的。如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意非确定性行为的风险,简单地来说,就是此类的方法都是非线程安全的。那如何解决呢?下面提供3个解决方法。
用Vector取代ArrayList
从Java 2平台v1.2开始,Vector被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的。正因为它是同步的,可以比较好解决并发修改问题。我们可以查看源码,可以得知Vector类与ArrayList类主要不同的点就是其增删改查的方法都用synchronized同步方法
下面举个例子来介绍其怎样解决ArrayList出现并发修改问题
package testJUC;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
public class TestList {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
//添加UUID随机字符串的截取索引1到5的字符串
list.add(UUID.randomUUID().toString().substring(1,5));
System.out.println(list);
},i+"").start();
}
}
}
运行结果如下
温馨提醒:由于使用多线程运行,结果是随机性的
用Collections.synchronizedList同步化ArrayList集合
要了解Collections.synchronizedList是怎样使用同步化ArrayList集合的,我们可以查看Javajdk源码
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
这个方法回根据你传入的List是否实现RandomAccess这个接口来返回的SynchronizedRandomAccessList还是SynchronizedList.
下面同意举个例子来介绍其怎样解决ArrayList出现并发修改问题
package testJUC;
import java.util.*;
public class TestList {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 20; i++) {
new Thread(() -> {
//添加UUID随机字符串的截取索引1到5的字符串
list.add(UUID.randomUUID().toString().substring(1,5));
System.out.println(list);
},i+"").start();
}
}
}
运行结果如下
用CopyOnWriteArrayList代替ArrayList
CopyOnWriteArrayList是jdk1.5出现的一个线程安全的变体ArrayList,该类的出现在保证效率的前提下,可以很好解决ArrayList非线程安全问题。下面我们来看源码了解其原理
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
从中可以看出,其主要是通过添加锁的作用来实现线程安全,而加锁的机制要比直接用synchronized锁住调用的对象的效率高,小编觉得这种方法要比前面两种更好。CopyOnWriteArrayList的设计还采用cow思想设计,这种思想是计算机设计领域一种优化策略,其添加方法为了写入覆盖原有的数据,采用每次写入时都对创建一个比原来数组长度大于1的数组,然后将原来数组复制到新数组。再将写入的值复制新数组的最后一个元素。
下面同意举个例子来介绍其怎样解决ArrayList出现并发修改问题
package testJUC;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestList {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
//添加UUID随机字符串的截取索引1到5的字符串
list.add(UUID.randomUUID().toString().substring(1,5));
System.out.println(list);
},i+"").start();
}
}
}
运行结果如下
总结
Vector类在其增删改查的方法都用synchronized同步方法,从而实现线程安全,但是效率会变低很多。Collections.synchronizedList也主要是添加synchronized来实现线程安全,这种方法也同样是效率变低很多。而CopyOnWriteArrayList通过添加锁lock来实现线程安全,而加锁的方法要比直接添加synchronized所对调用对象的效率要高。因此,小编建议使用第三种方法解决ArrayList线程安全。