引言:更多相关请看 JAVA并发编程系列
概述
指容器在并发修改争抢下会不会出现java.util.ConcurrentModificationException:并发修改异常。会就是不安全容器,不会(正常执行)就是安全容器。
List
不安全:ArrayList、LinkedList
安全:
Vector:1.0出现,Add方法加synchronized修饰。




Set
不安全:HashSet、LinkedHashSet、TreeSet。
安全:
Collections.synchronizedCollection/Set():synchronizedCollection是synchronizedList和synchronizedSet的父类。把一个普通不安全的的Set封装成线程安全的Set,Add方法加synchronized修饰。
CopyOnWriteArraySet:底层是CopyOnWriteArrayList()。

Sets.newCopyOnWriteArraySet():底层是CopyOnWriteArraySet。


Map
不安全:HashMap、LinkedHashMap、TreeMap。
安全:
Hashtable:put方法了synchronized修饰。




代码案例
// 安全与不安全容器
class ContainerDemo {
public static void main(String[] args) {
// 测试可能会爆java.util.ConcurrentModificationException:并发修改异常就是不安全容器,正常执行、完全不会爆就是安全容器
// listDemo();
// setDemo();
mapDemo();
}
/**
* 安全与不安全List
*/
public static void listDemo() {
// 不安全List
// List<String> list = new ArrayList<>();
// List<String> list = new LinkedList<>();
// 安全List
// List<String> list = new Vector<>();
// Collection<Object> list = Collections.synchronizedCollection(new ArrayList<>());
// List<String> list = Collections.synchronizedList(new ArrayList<>());
// List<String> list = new CopyOnWriteArrayList<>();
// 需引入google的guava依赖包
List<String> list = Lists.newCopyOnWriteArrayList();
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();
}
}
/**
* 安全与不安全Set
*/
public static void setDemo() {
// 不安全Set
// Set<String> set = new HashSet<>();
// Set<String> set = new LinkedHashSet<>();
// Set<String> set = new TreeSet<>();
// 安全Set
// Collection<Object> set = Collections.synchronizedCollection(new HashSet<>());
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
// Set<Object> objects = Collections.synchronizedSet(new HashSet<>());
// Set<String> set = new CopyOnWriteArraySet<>();
// 需引入google的guava依赖包
// Set<String> set = Sets.newCopyOnWriteArraySet();
Set<String> set = Sets.newConcurrentHashSet();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
/**
* 安全与不安全Map
*/
public static void mapDemo() {
// 不安全Map
// Map<Object, Object> map = new HashMap<>();
// Map<Object, Object> map = new LinkedHashMap<>();
// Map<Object, Object> map = new TreeMap<>();
// 安全Map
// Map<Object, Object> map = new Hashtable<>();
// Map<Object, Object> map = new ConcurrentHashMap<>();
// Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
// 需引入google的guava依赖包
Map<Object, Object> map = Maps.newConcurrentMap();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(UUID.randomUUID().toString().substring(0,3),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
总结
通过查看源码发现:安全容器的底层的get/put方法上或方法里总是有加锁。synchronized关键字或ReentrantLock可重入锁。也可以理解为:安全的本质就是加锁,避免并发修改争抢导致java.util.ConcurrentModificationException:并发修改异常。
写时复制
CopyOnWriteArrayList类add方法源码:
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 复制出一个新的object[] newElements,然后向新容器object[] newElements 里面添加元素。添加元素后,再将原容器的引用指向新的容器 setArray(newElements);
这样的好处是可以对copyOnWrite容器进行并发的读,而不需要加锁。因为当前容器不会添加任何容器.所以copyOnwrite容器也是一种读写分离的思想,读和写不同的容器。
与Vector相比:Vector所有的读写方法都加synchronized修饰
