总有一段难熬的日子,让你自我怀疑。不过当你再经历多一点,会发现,那只是生活的常态
在平时多线程操作集合类的时候,往往会遇到一些问题
List list = new ArrayList<>();
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();
}
结果抛出ConcurrentModificationException并发修改异常
Exception in thread "8" Exception in thread "20" Exception in thread "23" java.util.ConcurrentModificationException
[4cef5ee1, e77e63c5, b18fe499, 9a26690e, d0e109f2, f698862c, 453a9e46]
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
[4cef5ee1, e77e63c5, b18fe499, 9a26690e, d0e109f2, f698862c, 453a9e46, 32ef5223, 93584cff, 56a86b36]
...
并发争抢修改导致,参考我们的花名册签名情况
一个人正在写入,另外一个人过来抢,导致数据不一致异常。并发修改异常
3.1 new Vector<>()
3.2 Collections.synchronizedList(new ArrayList<>())
*3.3 CopyOnWriteArrayList<>() 写时复制
// List list = new Vector<>()();
// List list = Collections.synchronizedList(new ArrayList<>());
List list = new CopyOnWriteArrayList<>();
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();
}
结果
[e5c5137e, 6970878c, daec20d2]
[e5c5137e, 6970878c, daec20d2, be6f8c58, 034daf30, c3aa7d98, 588f9f91, a2e862c5]
[e5c5137e, 6970878c, daec20d2, be6f8c58, 034daf30, c3aa7d98, 588f9f91]
...
Process finished with exit code 0
写时复制
CopyOnWrite容器即写时复制的容器。往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将容器的应用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
源码分析
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//显示获取锁
lock.lock();
try {
//通过getArray()方法获取容器Object[]
Object[] elements = getArray();
//计算容器中元素的长度
int len = elements.length;
//将当前容器Object[]进行Copy,复制出一个新的容器Object[],容器大小加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//添加元素
newElements[len] = e;
setArray(newElements);
return true;
} finally {
//显示释放锁
lock.unlock();
}
}
public static void setNotSafe() {
//Set set = new HashSet<>();
// Set set = Collections.synchronizedSet(new HashSet<>());
Set set = new CopyOnWriteArraySet<>();
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();
}
}
new HashSet<>().add("e");
hashSet底层实现是hashMap,但hashMap.put(key,value),为什么add("e")一个key就可以了?
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//value是PRESENT恒定的
public static void mapNotSafe() {
// Map map = new HashMap<>();
// Map map = Collections.synchronizedMap(new HashMap<>());
Map map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}