5.并发下不安全的集合类如何解决?
感谢秦疆老师的JUC并发编程视频,更多了解哔哩哔哩搜索【狂神说Java】。
本文内容源于秦疆老师的JUC并发编程视频教程。给狂神推荐,点赞吧!
List 不安全
package demo4;
import java.util.ArrayList;
import java.util.UUID;
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
java.util.ConcurrentModificationException 并发修改异常
- 并发下ArraryList 不安全 扩容机制
解决方案: synchronized ()?
- Vector
List<String> list = new Vector<>();
Vector底层 add:
2. Collections.synchronizedList()
List<String> list = Collections.synchronizedList(new ArrayList<>())
- CopyOnWriteArrayList
//CopyOnWrite 写入时复制, COW 计算机程序设计领域的一种优化策略;
//多个线程调用的时候,list ,读取的时候,固定的,写入(覆盖)
//在写入的时候避免覆盖 ,造成数据问题!
//读写分离
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList 比 Vector牛逼在哪?CopyOnWriteArrayList 底层源码:Lock锁 ,synchronized 效率低
- 先会用,2. 货比三家,寻找其他的解决方案 3. 分析源码
Set不安全
package demo4;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class TestSet {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
解决方案:
- Collections.synchronizedSet()
Set<String> set = Collections.synchronizedSet(new HashSet<>());
- CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();
HashSet的底层是什么?
set的本质就是map的key 是无法重复的
public HashSet() {
map = new HashMap<>();
}
//add set的本质就是map的key 是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT 一个不变的值
private static final Object PRESENT = new Object();
Map不安全
回顾HashMap的基本操作
加载因子 初始化容量
//map 是这样用的嘛? 不是工作中不用HashMap?反正我用过,我也不晓得
//默认等价于什么? Map<String,String> map = new HashMap<>(16, (float) 0.75);
Map<String,String> map = new HashMap<>();
HashMap不安全
HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
package demo4;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MapTest {
public static void main(String[] args) {
//map 是这样用的嘛 不是?工作中不用HashMap?
//默认等价于什么?new HashMap<>(16, (float) 0.75);
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}).start();
}
}
}
解决方案:
- 使用线程安全的HashTable (效率低)
- ConcurrentHashMap的锁分段技术
ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();