一、ArrayList集合
1、现象
/**
* 1、可能会抛出
* java.util.ConcurrentModificationException(并发修改异常)
* 2、导致原因
*
* 3、解决方案
* @param
*/
public static void arrayList_notSafe(){
List<String> list = new ArrayList<>();
// 解决方案一:Vector 天生加锁
// List<String> list = new Vector<>();
// 解决方案二:使用 Collections.synchronizedList 完成集合的同步
// List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, String.valueOf(i)).start();
}
}
// java.util.ConcurrentModificationException
2、原因
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在ArrayList
的add方法并不能保证线程安全。
3、解决方案
- 1⃣️ 加锁
可以通过加锁来实现代码的同步,而JDK中本身就带有这样的实现。那就是Vector
集合。
- 2⃣️ 集合辅助类
JDK带了一个集合辅助类Collections
,里面提供了对不安全集合的安全包装,而ArrayList
集合就可以用里面的synchronizedList
进行包装。
- 3⃣️ 写时复制
使用CopyOnWriteArrayList
类。底层使用 ReentrantLock 完成集合的写时复制。会把集合拷贝一份,对副本进行操作,不会影响原集合的读,操作结束后将原集合替换掉,这样的好处就是可以对容器进行并发读,而不需要加锁。算是一种读写分离。
二、set集合
1、现象
public static void set_notSafe(){
Set set = new HashSet<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
// java.util.ConcurrentModificationException
2、原因
在运行上面这段代码的时候,大概率会遇到一个错误java.util.ConcurrentModificationException
,并发修改异常。由于HashSet
底层实现是HahsMap
,在add
的时候调用的是HashMap
的put
方法,所以在多线程的环境下抛出了异常。
3、解决方案
- 1⃣️ 加锁
可以通过加锁来实现代码的同步。
- 2⃣️ 集合辅助类
JDK带了一个集合辅助类Collections
,里面提供了对不安全集合的安全包装,而set
集合就可以用里面的synchronizedSet
进行包装。
- 3⃣️ 写时复制
使用CopyOnWriteArraySet
类。这个类Set
集合的实现类,底层是CopyOnWriteArrayList
,上面说到CopyOnWriteArrayList
通过写时复制来保证集合安全。
三、Map集合
Map
集合跟Set
集合的情况相似。JDK中提供了一个ConcurrentHashMap
类保证集合安全。