集合类不安全(写时复制)
我们知道
ArrayList
是线程不安全的,在多线程情况下运行会出现java.util.ConcurrentModificationException (并发修改异常)
1、故障现象
java.util.ConcurrentModificationException
(并发修改异常)2、导致原因
并发争抢修改。例如花名册签名,一个人写入,另一个人争抢,导致数据不一致,并发修改异常
3、解决方案
3.1 Vector
3.2
List<String> list = Collections.synchronizedList(new ArrayList<>());
3.3
new CopyOnWriteArrayList<>();
1、Vector
/**
* 集合类不安全的问题
* ArrayList
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
// UUID(唯一通用识别码)
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
这个时候我们来看源码 Vector的add方法,使用了
synchronized
关键字public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
2、Collections.synchronizedList(new ArrayList<>());
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
// UUID(唯一通用识别码)
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
使用
Collections
帮助类,synchronizedList()
方法。
3、new CopyOnWriteArrayList<>();
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
// UUID(唯一通用识别码)
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
以下是CopyOnWriteArrayList
的源码
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 1
lock.lock();
try {
// 2
Object[] elements = getArray();
// 2.1
int len = elements.length;
// 3
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 4
newElements[len] = e;
// 5
setArray(newElements);
// 6
return true;
} finally {
// 7
lock.unlock();
}
}
解释一下代码,继续用花名册的例子。
1、首先
lock()
,我张三拿到了唯一一只笔要在花名册上签名。2、
elements
,相当于没有张三名字的名单3、
Arrays.copyOf
拷贝!在老版本的基础上扩容1。张三只签自己的 名字4、我们假设 2.1 中
len
已经有了10个名字。在第3步中。len + 1
,就是在数组下标11的地方写上张三的名字也就是第四步中的e
。 (数组下标是从0开始,这里方便理解从1开始)5、之后在把有张三名字的
newElements
。set会名单里6、通知下一个人我写完了
7、释放锁,把笔交给另外一个人
这就是写时复制
CopyOnWrite
容器即写时复制容器。往一个容器添加元素的时候,不直接往当前容器Object[]
添加,而是先将容器Object[]
进行Copy
,复制出一个新的容器Object[] newElements
,然后往新的容器Object[] newElements
里添加元素,添加完元素之后,再讲原容器的引用指向新的容器setArray(newElements);
。这样做的好处就是可以对CpoyOnWrite
容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CpoyOnWrite
容器也是一种读写分离的思想,读和写不同的容器。