ArrayList线程不安全与Vector线程安全

原因解释

首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
这里写图片描述
如图,List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,
sun公司希望Vector是线程安全的,而希望arraylist是高效的.

ArrayList线程不安全解释

首先我们来看一下ArrayList添加元素时的源码

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

我们注意到elementData[size++] = e;这条语句其实就是添加元素的关键,其实这条语句可以拆成两条语句来执行也就是elementData[size] = esize++,试想一下,如果在多线程访问同一个list的时候,线程A执行到elementData[size] = e时,时间片到了让出CPU,此时线程B执行,当线程B执行到elementData[size] = e此时size还没有加一,(假设此时size=10)也就是说线程A在list[10]位置上添加的元素被线程B添加的元素覆盖了,然后A,B两个线程都执行size++;这样造成的影响就是size变成12了,但是在第11个位置上却没有元素也就是说list[11]=null我嗯通过下面一段代码来验证下:

package com.github.thread;

import java.util.List;

/**
 *@DESCRIPTION 线程类,用来操作list
 *@AUTHOR SongHongWei
 *@TIME 2018/8/30-9:05
 *@PACKAGE_NAME com.github.thread
 **/
public class ListTask implements Runnable
{
    private List<String> list;

    ListTask(List list)
    {
        this.list = list;
    }

    @Override
    public void run()
    {
       try {
            Thread.sleep(10);//线程睡眠10ms
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //把当前线程名称加入list中
        list.add(Thread.currentThread().getName());
    }

    public List<String> getList()
    {
        return list;
    }

    public void setList(List<String> list)
    {
        this.list = list;
    }
}

测试类

package com.github.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

/**
 *@DESCRIPTION 多线程, 线程池, 队列
 *@AUTHOR SongHongWei
 *@TIME 2018/8/22-17:08
 *@PACKAGE_NAME prodmng.songhw.thread
 **/
public class Client
{
    public static void main(String[] args)
        throws Exception
    {
        //List<String> list = new Vector(); //线程安全
        List<String> list = new ArrayList<>();//线程不安全
        ListTask listThread = new ListTask(list);//实例化线程类
        for (int i = 0; i < 100; i++)
        {
            Thread thread = new Thread(listThread, String.valueOf(i));//循环新开线程
            thread.start();
        }

        //等待子线程执行完
        Thread.sleep(2000);

        System.out.println("list的总长度为"+listThread.getList().size());
        //输出list中的值
        for (int i = 0; i < listThread.getList().size(); i++)
        {
            System.out.print(listThread.getList().get(i) + "  ");
        }

    }
}

执行结果为:

list的总长度为98
null null 0 3 1 5 4 13 14 10 15 11 8 7 9 16 12 27 21 28 20 26 25 23 19 17 24 22 18 39 36 33 38 34 35 30 32 31 37 29 49 46 42 43 41 44 60 47 51 52 58 59 54 55 56 45 53 50 57 48 40 69 65 68 70 62 61 71 63 66 64 74 77 80 72 76 82 78 79 73 75 81 83 89 86 84 88 90 87 94 92 91 85 93 99 95 96 98
Process finished with exit code 0

根据执行结果我们看出,实际上我期待的list结果应该为100,而不是98,并且list里面还出现了null元素

其实不止会出现上述问题,多执行几次我们会发现,程序居然还会抛出异常,下面给出异常错误信息:

Exception in thread "69" java.lang.ArrayIndexOutOfBoundsException: 73
    at java.util.ArrayList.add(ArrayList.java:441)
    at com.github.thread.ListTask.run(ListTask.java:49)
    at java.lang.Thread.run(Thread.java:744)
list的总长度为98
0   1   2   18  10  8   5   17  6   14  11  21  13  19  20  4   12  15  22  27  30  26  28  25  29  24  7   16  23  34  33  35  36  31  32  40  42  39  43  38  37  41  46  45  47  48  54  50  51  44  52  59  53  66  58  60  62  61  67  65  57  49  64  55  56  63  81  73  79  74  78  80  70  
null    75  76  77  71  68  72  82  90  88  84  87  91  86  83  89  85  95  99  92  96  94  97  93  98  

从上面结果可以看出,程序抛出了一个ArrayIndexOutOfBoundsException异常以及list中出现了null元素两个问题,null元素出现的原因已经解释过了,现在我们来分析ArrayIndexOutOfBoundsException异常出现的原因

直观上理解ArrayIndexOutOfBoundsException应该是数组下标越界,我们再来看一下ArrayList新增元素的源码

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

之前我们关注的是elementData[size++] = e语句,现在我们来看看ensureCapacityInternal(size + 1);这条语句是干嘛的,其实ensureCapacityInternal这个方法主要是对List的扩容,顺便提一下,List的默认容量是10,每次扩容大小为原来的1.5倍,源码的扩容方式是通过以为来运算,这样的运算效率更高(int newCapacity = oldCapacity + (oldCapacity >> 1);)好了,言归正传,知道了ensureCapacityInternal是扩容list后我们来模拟一个场景,假设A线程执行到ensureCapacityInternal时,此时的size=9,还未到达扩容的条件,然后线程A让出CPU,线程B又进来执行,当线程B执行到ensureCapacityInternal(size+1)时,由于线程A还没有进行添加元素的操作,所以此时size=9,所以线程B此时也没有对List进行扩容,接着线程B继续执行,添加元素,size+1,这时候size=10,就在这时B线程执行结束,A线程继续执行,由于刚才A线程已经判断过不需要扩容,所以直接添加元素,但问题是B线程执行后size=10了,A线程再往里添加元素自然就报数组下标越界了.

Vector 线程安全

Vector 源码里对add操作加了同步锁,所以不会造成线程安全问题,但是由于加了同步锁,所以执行效率上就成了问题
Vector新增元素的源码

 /**
     * Adds the specified component to the end of this vector,
     * increasing its size by one. The capacity of this vector is
     * increased if its size becomes greater than its capacity.
     *
     * <p>This method is identical in functionality to the
     * {@link #add(Object) add(E)}
     * method (which is part of the {@link List} interface).
     *
     * @param   obj   the component to be added
     */
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

运行结果

list的总长度为100
2   0   1   8   11  7   10  13  9   6   5   4   3   21  14  17  19  18  12  15  23  16  24  22  20  36  32  31  34  37  26  30  29  35  25  33  27  28  53  41  51  52  46  49  47  42  61  40  59  38  60  43  39  62  44  54  67  48  45  50  56  64  58  63  66  72  83  55  76  80  75  79  78  68  71  70  73  82  74  81  77  69  57  65  97  90  92  95  88  85  94  91  87  86  96  99  98  89  93  84  
Process finished with exit code 0

其他线程安全的方法

由于Vector已经不建议使用了,所以我们如果想使用线程安全的List时可以使用

List<String> list1 = Collections.synchronizedList(new ArrayList<String>());

这种方式去实现

猜你喜欢

转载自blog.csdn.net/u010859650/article/details/82217791
今日推荐