为什么说ArrayList是线程不安全的?

http://blog.csdn.net/jiaochunyu1992/article/details/51177373


概要介绍 
首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 如图,List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的,缺点就是另外的优点。 说下原理(百度的,很好理解): 一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成: 
1. 在 Items[Size] 的位置存放此元素; 
2. 增大 Size 的值。 
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1; 
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。 


众所周知,ArrayList作为Collection中极重要的一员,是非线程安全的。那么它的非线程安全体现在哪些地方呢?下面我就具体分析这个问题。不对之处,欢迎大家指正。

在讨论这个问题之前,先说下线程安全的三个重要特性:操作原子性、状态一致、内存可见性。

操作原子性:该操作只能一口气做完,中间不能停顿。

状态一致性:共享对象的状态要一致。

内存可见性:每个线程都有自己的工作空间,它使用某个对象时,先将对象从主存复制到它的工作空间,然后再对对象做修改,最后将修改写入主存。那么就会出现一个问题:多个线程在同一时刻保存的同一共享对象的值可能不一样,因为可能有线程并没有及时将对对象的修改及时写入主存并通知其他线程。

它们有什么作用呢?原子性操作保证了共享对象的状态一致,而状态一致则表示着该对象处于正确的状态,内存可见性则保证了所有线程在同一时刻都取到了相同的共享变量。因此我们在多线程编程中,需要关注保证共享变量的操作原子性、状态的一致以及内存可见性。

1. 操作的非原子性

ArrayList的方法都是非原子性的,因为这些方法可以同时被多个线程访问,并且可能在任何地方终止。要保证操作原子性的方法有很多,如使用synchronized关键字修饰方法或者包围住改变对象状态的操作;使用Lock包裹代码段(相对与synchronized,Lock提供了更灵活的处理方式,也带来了更好的并发性能);使用Java并发包下的原子变量;使用CAS。

2. 状态不一致

分析它的状态不一致,通常是多个线程同时调用两个不兼容的方法导致的。下面以size和remove为例。

源码

[java]  view plain  copy
  1. /** 
  2.  * Returns the number of elements in this list. 
  3.  * 
  4.  * @return the number of elements in this list 
  5.  */  
  6. public int size() {  
  7.     return size;  
  8. }  

[java]  view plain  copy
  1. /** 
  2.  * Removes the element at the specified position in this list. 
  3.  * Shifts any subsequent elements to the left (subtracts one from their 
  4.  * indices). 
  5.  * 
  6.  * @param index the index of the element to be removed 
  7.  * @return the element that was removed from the list 
  8.  * @throws IndexOutOfBoundsException {@inheritDoc} 
  9.  */  
  10. public E remove(int index) {  
  11.     rangeCheck(index);  
  12.   
  13.     modCount++;  
  14.     E oldValue = elementData(index);  
  15.   
  16.     int numMoved = size - index - 1;  
  17.     if (numMoved > 0)  
  18.         System.arraycopy(elementData, index+1, elementData, index,  
  19.                          numMoved);  
  20.     elementData[--size] = null// clear to let GC do its work  
  21.   
  22.     return oldValue;  
  23. }  

size很简单,直接返回了。

remove操作分为三部分:边界检查;检查是否需要移动元素;清除该位置元素。

假设现在有两个线程A、B,A执行size(),B执行remove(); A进入函数,取得size的值后切换到B继续执行(非原子操作),B执行完整个方法,切换到A。注意,此时size值已经被改变了,因此A拿到的size是失效的(修改不可见),但是A并不知道,它一如既往的执行下去,就想单线程中一样。所以当我们想要通过取得的size值做一些操作(删除最后一个元素,取得最后一个元素),很明显会越界。

上面的情况并没有提到状态不一致的问题,但是它是明确存在的,比如多个线程同时执行remove方法时。





示例程序:

package test;

import java.util.ArrayList;
import java.util.List;

public class ArrayListInThread implements Runnable {
    List<String> list1 = new ArrayList<String>();// not thread safe

//    List<String> list1 = Collections.synchronizedList(new ArrayList<String>());// thread safe
    public void run() {
        try {
            Thread.sleep((int)(Math.random() * 2));
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        list1.add(Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadGroup group = new ThreadGroup("mygroup");
        ArrayListInThread t = new ArrayListInThread();
        for (int i = 0; i < 10000; i++) {
            Thread th = new Thread(group, t, String.valueOf(i));
            th.start();
        }

        while (group.activeCount() > 0) {
            Thread.sleep(10);
        }
        System.out.println();
        System.out.println(t.list1.size()); // it should be 10000 if thread safe collection is used.
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值