今天在学习集合Collection,里面讲到了List下面的实现类ArrayList、LinkedArrayList和Vector的线程安全问题。
先抛出结论:
ArrayList和LinkedList是线程不安全的,Vector是线程安全的。
分析:
线程的安全性是对于多线程来说的,如果是单线程的程序,可以不用考虑安全问题。
以ArrayList和Vector的add方法为例,先看源码。
可以看到,Vecrot使用了synchronized关键字(不知道什么意思的话,自行百度),所以它是安全的,ArrayList就是不安全的。为什么会这么说,原因如下:
用elementData[size++] = e 这一步作为例子来讲。
从这儿可以看出,这步操作也不是一个原子操作,它由如下两步操作构成:
elementData[size] = e;
size = size + 1;
在单线程执行这两条代码时没有任何问题,但是当多线程环境下执行时,可能就会发生一个线程的值覆盖另一个线程添加的值,具体逻辑如下:
列表大小为0,即size=0
线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
线程A开始将size的值增加为1
线程B开始将size的值增加为2
这样线程AB执行完毕后,理想中情况为size为2,elementData下标0的位置为A,下标1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成
了B,下标1的位置上什么都没有。并且后续除非使用set方法修改此位置的值,否则将一直为null,因为size为2,添加元素时会从下标为2的位置上开始。
写了一段代码测试下
/**
* 测试线程安全性
*/
@Test
public void test3() {
List<Integer> list = new ArrayList<>();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 1000; i++) {
list.add(i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for (int i = 1000; i < 5000; i++) {
list.add(i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印所有结果
for (int i = 0; i < list.size(); i++) {
System.out.println("第" + (i + 1) + "个元素为:" + list.get(i));
}
}
多跑几次,就会发现,会有元素为null的打印出来,我测试的是第11个元素丢了,本来应该是5的。
如果想避免这种问题,可以再add方法那里加上synchronized,对list做下同步就好了。代码如下: