JUC ArrayList为什么不是线程安全的&如何安全

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

引言

最近在学习群里看到群友们在讨论一个问题:并发的向ArrayList里添加10000个元素元素,最后打印出ArrayList的size(),打印结果小于10000,便在群里问这事怎么回事?

首先我们针对三个小伙伴的代码挨个来点评下:

代码检视

第一位

971637857636_.pic.jpg
首先,这个小伙伴饭了一个非常恐怖的错误, for循环里面创建了一万个线程,这对程序来说无疑是一个灾难。虽然这里是一个单机的Demo程序,对于资深的程序员来说,有些东西是要刻在DNA里面的。虽然Java程序员不能像C++程序员对每一块内存都如数家珍,但也不能这么暴殄天物。

其次,这位同学不知道在哪里学到的这个等待线程全部执行完成的方法,while(Thread.activeCount() > 2)很神奇,他还解释说,如果是IDEA这里写2,如果用Eclipse这里写1。说实话还有点可爱。(说明:此方法仅能在运行单机Demo时测试用,在生产环境实不可取的。)好的地方是他考虑到了主线程会早于new出来的线程结束,所以让CPU空转来等待其他线程完成。

说完了编码上的硬伤,我们来看一下这位同学对并发安全的理解:没有理解。他完全没有做任何线程安全的措施,所以输出的结果 9990 是不符合预期的,好吧,我们来看下一位。

第二位

image.png

image.png

在得到其他童鞋的指点后,第二位同学马上做出了调整

第二位的第二次尝试

image.png

image.png

第二位同学放弃了。

ArrayList的线程安全性

大家都知道ArrayList不是线程安全的,那他究竟是怎么不安全了。如何让他变得安全,今天我们就来看看。

啪,很快啊,我就写出了一串代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%