集合类线程不安全问题
学习地址:https://www.bilibili.com/video/BV18b411M7xz?p=20
问题现象
从ArrayList入手
public static void main(String[] args) {
testArrayList();
// testHashSet();
// testHashMap();
}
public static void testArrayList() {
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();
}
}
报错java.util.ConcurrentModificationException
导致原因
线程并发争抢修改导致,参考给花名册签名情况。
一个人正在写入,另一个同学过来抢夺,导致数据不一致异常。并发修改异常。
解决方案
-
new Vector<>();
-
JDK1.0就存在,ArrayList是JDK1.2出现
-
//查看源码得知,Vector是通过加sync实现线程安全,降低了效率 public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
-
-
Collections.synchronizedList(new ArrayList<>());
-
使用集合的包装方法
-
//查看源码得知,Collections提供了sync的方法,实现线程安全 public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); }
-
-
new CopyOnWriteArrayList<>()
-
实现了读写分离,即实现了添加,又将读操作和写操作分离开,不影响效率
-
/** CopyOnWrite容器即写时复制的容器。 * 往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前object[]进行Copy,复制出一个新的容器Object[] newElements, * 然后新的容器Object[] newElements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements); * 这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。 * 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。*/ public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
-
补充HashSet、HashMap
public static void testHashSet() {
Set<String> set1 = new HashSet<>();
Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
new HashSet<>();//底层HashMap<>();
/**
* HashMap<>()是k-v键值对,HashSet实现时只保留key值,value是PRESENT 一个常量
* public boolean add(E e) {
* return map.put(e, PRESENT)==null;
* }
*/
new HashSet<>().add("a");//
}
public static void testHashMap() {
Map<String,String> map1 = new HashMap<>();
Map<String,String> map2 = Collections.synchronizedMap(new HashMap<>());
//注意HashMap使用new ConcurrentHashMap<>()
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
优化建议
ArrayList、HashSet推荐使用new CopyOnWriteArrayXXX();
HashMap推荐使用new ConcurrentHashMap<>();