集合类线程不安全
集合类是线程不安全
证明集合类是线程不安全
文章目录
前言
在多线程编程中,日常用的集合类存在并发情况下,可变操作(add,set,等)无法保证数据一致的情况
提示:以下是本篇文章正文内容,下面案例可供参考
一、证明集合不安全
1.代码演示:
代码演示如下(示例):
package com.study.thread;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 模拟多线程添加数据,并打印
*/
public class NoSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
此时出现的结果(示例):
Exception in thread "0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
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.study.thread.NoSafeDemo.lambda$main$0(NoSafeDemo.java:13)
at java.lang.Thread.run(Thread.java:748)
说明:这个异常类为ConcurrentModificationException,直译过来就是并发修改异常,源码说明为,只有在修改被拒绝时才会被检测到,由此可得知,并发情况下,在ArrayList中调用add()方法的同时对其进行修改会抛出java.util.ConcurrentModificationException异常。
ArrayList中的add(E e)方法源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
没有synchronized关键字导致线程不安全
2.解决方案1,使用Vector集合(不推荐使用,官网已放弃):
代码如下(示例):
package com.study.thread;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
/**
* 模拟多线程添加数据,并打印
*/
public class NoSafeDemo {
public static void main(String[] args) {
List<String> list = new Vector<>(); //将ArrayList改成Vector
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();
}
}
}
使用Vector集合类,是不会抛ConcurrentModificationException
Vector的add(E e)源码如下:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
有synchronized线程安全
一般不使用Vector,Vector类中,大多数方法都是直接使用synchronize关键字修饰,性能上会差很多,线程安全的实现方法比较粗暴且效率低。(synchronize关键字修饰的方法,是能够保证线程安全,但是会阻塞其他线程的访问其他synchronize修饰的方法,而vector恰好大多数方法都使用synchronize关键字修饰,其直接结果就是导致效率插入,删除时,效率低)
引申:java.util,Stack因为extends Vector,所以也不推荐使用,可以使用java.util.Deque双端队列来实现队列与栈的需求。
2.解决方案2,使用Collections集合工具类:
代码如下(示例):
package com.study.thread;
import java.util.List;
import java.util.UUID;
import java.util.Collections;
/**
* 模拟多线程添加数据,并打印
*/
public class NoSafeDemo {
public static void main(String[] args) {
//使用集合工具类Collections中的synchronizedList(List<T> list)
List<String> list = Collections.synchronizedList(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();
}
}
}
使用集合工具类Collections中的synchronizedList(List list),
是不会抛ConcurrentModificationException
Collections的synchronizedList(List list)源码如下:
返回一个指定的集合线程安全list,来保证同步线程安全
同样对于HashMap<K,V>,HashSet<K,V>在Collections工具类中也有对应的线程安全方法
HashMap<k,v>保证线程安全如下:
HashSet<k,v>线程安全如下:
Collections作为java.uril中的工具类,包含了常用的集合的线程安全操作
2.解决方案3,使用CopyOnWriteArrayList集合:
代码如下(示例):
package com.study.thread;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 模拟多线程添加数据,并打印
*/
public class NoSafeDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
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.concurrent中的CopyOnWriteArrayList,是不会抛ConcurrentModificationException
CopyOnWriteArrayList源码说明,CopyOnWriteArrayList是ArrayList的一种线程安全的变体,其中所有的可变操作(add,set,等)都是通过实现底层array(数组)生产新副本来实现的
CopyOnWriteArrayList的add(E e)源码如下:
通过对add方法加锁的方式来实现线程安全操作
CopyOnWrite容器即写时复制的容器,往容器中添加元素时,不是直接向当前容器Object[] elements中添加,而是通过对当前容器进行Arrays.copyOf()复制一个新的容器Object[] newElements,然后想新容器中添加元素newElements[len] = e,添加元素后,再将原容器的引用指向新容器setArray(newElements)
这样做的好处是,可以对CopyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素,所以,CopyOnWrite容器也是一种读写分离的思想,读和写在不同的容器。
3.拓展类比
HashSet<>()线程不安全
CopyOnWriteArraySet 线程安全
HashMap<>() 线程不安全
ConcurrentHashMap<K,V> 线程安全
待说明
总结
本节总结了线程并发时,集合的可变操作(add,set等)安全性分析,使用了3种不同的方式对List集合进行thread-safe说明,vector,collections,copyOnWriteArrayList,其中copyOnWriteArrayList为java.util.concurrent并发包下的一个线程安全类,能够保证操作list时候的线程安全,同时解释了在并发情况下,add操作线程安全的原理。