今天在学习集合Collection,里面讲到了List下面的实现类ArrayList、LinkedArrayList和Vector的线程安全问题。

先抛出结论:

    ArrayList和LinkedList是线程不安全的,Vector是线程安全的。

分析:

    线程的安全性是对于多线程来说的,如果是单线程的程序,可以不用考虑安全问题。

    以ArrayList和Vector的add方法为例,先看源码。

    

java线程安全的数组 java 线程安全 list_i++

java线程安全的数组 java 线程安全 list_i++_02

    可以看到,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的。

java线程安全的数组 java 线程安全 list_线程安全_03

如果想避免这种问题,可以再add方法那里加上synchronized,对list做下同步就好了。代码如下:

java线程安全的数组 java 线程安全 list_i++_04