这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战
引言
最近在学习群里看到群友们在讨论一个问题:并发的向ArrayList里添加10000个元素元素,最后打印出ArrayList的size(),打印结果小于10000,便在群里问这事怎么回事?
首先我们针对三个小伙伴的代码挨个来点评下:
代码检视
第一位
首先,这个小伙伴饭了一个非常恐怖的错误, for循环里面创建了一万个线程,这对程序来说无疑是一个灾难。虽然这里是一个单机的Demo程序,对于资深的程序员来说,有些东西是要刻在DNA里面的。虽然Java程序员不能像C++程序员对每一块内存都如数家珍,但也不能这么暴殄天物。
其次,这位同学不知道在哪里学到的这个等待线程全部执行完成的方法,while(Thread.activeCount() > 2)很神奇,他还解释说,如果是IDEA这里写2,如果用Eclipse这里写1。说实话还有点可爱。(说明:此方法仅能在运行单机Demo时测试用,在生产环境实不可取的。)好的地方是他考虑到了主线程会早于new出来的线程结束,所以让CPU空转来等待其他线程完成。
说完了编码上的硬伤,我们来看一下这位同学对并发安全的理解:没有理解。他完全没有做任何线程安全的措施,所以输出的结果 9990 是不符合预期的,好吧,我们来看下一位。
第二位
在得到其他童鞋的指点后,第二位同学马上做出了调整
第二位的第二次尝试
第二位同学放弃了。
ArrayList的线程安全性
大家都知道ArrayList不是线程安全的,那他究竟是怎么不安全了。如何让他变得安全,今天我们就来看看。
啪,很快啊,我就写出了一串代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* ArrayList线程安全实践
*/
public class ArrayListSecturyTest {
public static void main(String[] args) throws InterruptedException {
final List<Integer> list = new ArrayList();
int num = 10000;
ExecutorService e = new ThreadPoolExecutor(10, 10, 5L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(num));
final CountDownLatch countSign = new CountDownLatch(num);
for (int i = 0; i < num; i++) {
final int finalI = i;
e.execute(new Runnable() {
public void run() {
synchronized (list) {
list.add(finalI);
countSign.countDown();
}
}
});
}
countSign.await();
e.shutdown();
System.out.println(list.size());
}
}
简单说下思路:
// todo
创建一个线程池,然后使用
完整代码: gitee.com/StephenRead…
执行结果:
不安全的版本 用时:4370 ms result: 9984520
安全的版本 - 使用synchronize 用时:3410 ms result: 10000000
安全的版本 - 使用ReentrantLock 用时:6688 ms result: 10000000
安全的版本 - 使用Collections.synchronizedList(new ArrayList<>()) 用时:3033 ms result: 10000000
Process finished with exit code 0