集合类的安全性及解决
1. List
- 举例
public class JUC05_NotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//方案 1
// List<String> list = new Vector<>();
//方案 2
// List<String> list = Collections.synchronizedList(new ArrayList<>());
//方案 3
// List<String> list = new CopyOnWriteArrayList<>();
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
- 原因
- 多个线程几乎同时对一个 List 进行操作,相互争抢,List 承受不了
- 解决方案
-
方案 1
- 把 ArrayList 换成 List list = new Vector<>();即可
- 因为 ArrayList 线程不安全,Vector 线程安全
- Vector 底层的 add 方法已经加了 synchronized 同步锁
- 使用 Vector 数据一致性增强,访问性能下降
-
方案 2
- List list = Collections.synchronizedList(new ArrayList<>());
- 在数据量小的时候,可以使用
- 扩展
- HashMap、HashSet 也都是线程不安全
-
方案 3 ⭐
- List list = new CopyOnWriteArrayList<>();(写时复制技术)
- 写时复制原理:
- 第一个线程来了,把(原本的资源类)(1.0)复制一份,自己去写
- 原本的资源类,供其他线程读
- 第一个线程在写的时候,对复制的那一份加锁,写完了解锁,用(更新的资源类)(2.0)替换原本的
- 第二个线程,就复制(2.0)版本的资源类写
- 。。。
- 源码
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();
}
}
- 分析
- CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy (使用 Arrays.copyof() 进行扩容) ,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
- 添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
- 这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
- 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
2. Set
- 解决方案
-
方案 1
- Set set = Collections.synchronizedSet(new HashSet<>());
- 在数据量小的时候,可以使用
-
方案 2
- Set set = new CopyOnWriteArraySet<>();(写时复制技术)
- 补充
- HashSet 底层是 HashMap
- HashSet 的 add 就是调用的 HashMap 的 push ,不过就是把 Value 设为一个固定的 Object 类型的常量
- private static final Object PRESENT = new Object();
3. Map
- 解决方案
- 方案 1
- Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
- 方案 2
- Map<String,String> map = new ConcurrentHashMap<>();//线程安全
- 补充
- HashMap 底层是 Node类型的数组 + 链表 + 红黑树
4. 优化建议
- 使用 Map 时 ,按照具体业务的需求把容量设置为一个较大的值