JAVA集合类的不安全
一、并发修改异常——ConcurrentModificationException
我们都知道ArrayList是线程不安全的,那么先举一个线程不安全的case,来看看到底怎么个不安全。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i=1;i<30;i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 6));
System.out.println(list);
},String.valueOf(i)).start();
}
}
以上代码运行结果
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.zhangfz.day02.NoSafeDemo.lambda$list$2(NoSafeDemo.java:64)
那到底问题出在哪呢?
在多线程的情况下,一个线程正在执行写操作当执行到一半时,突然另一个X线程抢到了执行权,也要执行写操作,而这时候就会导致并发修改异常。
解决办法:
- Vector集合可以解决。因为它的add方法加锁了,但是并不推荐使用,因为一旦加锁时间可以保证数据的一致性但却牺牲了并发性,也就是说没有效率
- Collections.synchronizedList(new ArrayList<>());实用工具类中的方法创建一个线程安全的集合,此种方法也不推荐
- CopyOnWriteArrayList();写时复制技术推荐使用
二、写时复制技术
什么是写时复制呢,那正如起名就是在写的时候复制一份出来去执行写操作。下面看源码
首先获取数组中的数据,然后获取数组长度。之后使用Arrays.copyOf()重新复制一个长度+1的数组,把要写入的数据加到最后,并set到数组中返回true.
使用写时复制可以让读的时候去读原来那一份数据,写的时候保证原子性。这也是一种读写分离的一种思想,也就是并发读,原子写。
三、Set
同样HashSet也是线程不安全的,其解决方案有两种:
- Collections.synchronizedSet();使用工具类(不推荐)
- new CopyOnWriteArraySet<>();JUC写时复制技术,原理同CopyOnWriteArrayList.
四、Map
Set不安全 ,而HashSet的底层是HashMap。那HashMap 自然也是不安全的
但Map的解决方案稍微不同,JUC 中并没有CopyOnWrite…的集合类,但是它有一个只有ConcurrentHashMap的集合类,其就是用来解决HashMap的不安全的