多线程操作ArrayList不安全解决方案
- 1. 需求概述
- 2. 需求验证
- 2.1 测试方案
- 2.2 验证结论
- 2.3 代码实现
- 3. 解决方案
- 3.1 Vector
- 3.1 Collections工具类
- 3.1 CopyOnWriteArrayList
1. 需求概述
我们都知道List,ArrayList,以及Map,HashMap等常用的集合都是线程不安全的,替代及解决方案都是对应的current包下的对应的结合容器,比如hashMap对应CurrentHashMap,ArrayList对应什么呢,vector,也可以,就是效率太低,首选CopyOnWriteArrayList,读写分离,可重入锁效率更高。
以下就是测试验证以及原理分析。
2. 需求验证
2.1 测试方案
测试案例采用两种方式ArrayList与CopyOnWriteArrayList作为集合容器,同时开启多线程分别往两个容器内插入数据。
2.2 验证结论
最终结果是ArrayList数据丢失,而CopyOnWriteArrayList数据正常。
2.3 代码实现
package com.zrj.unit.collection;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* 多线程操作ArrayList
*
* @author zrj
* @since 2021/12/17
**/
public class ThreadArrayList {
// 自定义线程名称
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
// 定义线程池
private static ExecutorService pool = new ThreadPoolExecutor(30, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
/**
* 多线程下ArrayList类线程不安全的解决方法及原理
* 测试验证
* 【simpleList】休眠30秒等待线程执行完成,理论长度6000,实际长度:5426
* 【simpleCopyOnWriteList】休眠30秒等待线程执行完成,理论长度6000,实际长度:6000
* 解决方案
* 1.首选CopyOnWriteArrayList,读写分离,可重入锁。
* 2.其次vector,synchronized效率低下,逐渐被遗弃。
*/
@SneakyThrows
public static void main(String[] args) {
//验证ArrayList
List<String> simpleList = new ArrayList<>(100);
//启动30个线程
for (int i = 0; i < 30; i++) {
pool.execute(() -> {
//每个线程插入100条数据
for (int j = 0; j < 100; j++) {
simpleList.add("simpleList");
}
});
}
for (int i = 0; i < 30; i++) {
pool.execute(() -> {
//每个线程插入100条数据
for (int j = 0; j < 100; j++) {
simpleList.add("simpleList");
}
});
}
Thread.sleep(20000);
System.out.println("【simpleList】休眠20秒等待线程执行完成,理论长度6000,实际长度:" + simpleList.size());
//验证CopyOnWriteArrayList
CopyOnWriteArrayList<String> simpleCopyOnWriteList = new CopyOnWriteArrayList<>();
//启动30个线程
for (int i = 0; i < 30; i++) {
pool.execute(() -> {
//每个线程插入100条数据
for (int j = 0; j < 100; j++) {
simpleCopyOnWriteList.add("simpleList");
}
});
}
for (int i = 0; i < 30; i++) {
pool.execute(() -> {
//每个线程插入100条数据
for (int j = 0; j < 100; j++) {
simpleCopyOnWriteList.add("simpleList");
}
});
}
Thread.sleep(20000);
System.out.println("【simpleCopyOnWriteList】休眠20秒等待线程执行完成,理论长度6000,实际长度:" + simpleCopyOnWriteList.size());
}
}
3. 解决方案
3.1 Vector
Vector类 是可以实现自动增长的对象数组,其add操作是用synchronized关键字修饰的,从而保证了add方法的线程安全。保证了数据的一致性,但由于加锁导致访问性能大大降低。
vector源码分析
public synchronized boolean add(E var1) {
++this.modCount;
this.ensureCapacityHelper(this.elementCount + 1);
this.elementData[this.elementCount++] = var1;
return true;
}
3.1 Collections工具类
用Collections工具类将线程不安全的ArrayList类转换为线程安全的集合类。小体量数据的ArrayList类可以使用这种方法创建线程安全的类。
List<String> list=Collections.synchronizedList(new ArrayList);
3.1 CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
public boolean add(E e) {
//1、先加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//2、拷贝数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//3、将元素加入到新数组中
newElements[len] = e;
//4、将array引用指向到新数组
setArray(newElements);
return true;
} finally {
//5、解锁
lock.unlock();
}
}