java中ArrayList线程不安全和vector线程安全

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43407007/article/details/87901795

        Vector实现了List接口,与ArrayList一样可以维护一个插入顺序,但ArrayList比Vector快,它是非同步的,若涉及到多线程,用Vector会比较好一些,在非多线程环境中,Vector对于元素的查询、添加、删除和更新操作效果不是很好。
        Vector 可实现自动增长的对象数组。 java.util.vector提供了向量类(vector)以实现类似动态数组的功能。创建了一个向量类的对象后,可以往其中随意插入不同类的对象,即不需顾及类型也不需预先选定向量的容量,并可以方便地进行查找。对于预先不知或者不愿预先定义数组大小,并且需要频繁地进行查找,插入,删除工作的情况。可以考虑使用向量类。


1.Vector的使用

        向量类提供类三种构造方法:

public vector()
public vector(int initialcapacity)
public vector(int initialcapacity,int capacityIncrement)

    1
    2
    3

        方法一是系统自动对向量进行管理,创建初始容量为10的空的Vector;若再增加元素,则成倍的增长;
        方法二根据初始参数initialcapacity创建Vector对象
        方法三中参数capacityincrement给定了每次扩充的扩充值。当capacityincrement为0的时候,则每次扩充一倍,利用这个功能可以优化存储。
        在Vector类中主要提供了如下方法:
         1)public final synchronized void adddElement(Object obj)
        将obj插入向量的尾部。obj可以是任何类型的对象。对同一个向量对象,亦可以在其中插入不同类的对象。但插入的应是对象而不是数值,所以插入数值时要注意将数组转换成相应的对象。
        例如:要插入整数1时,不要直接调用v1.addElement(1),正确的方法为:

    Vector v1 = new Vector();
    Integer integer1 = new Integer(1);
    v1.addElement(integer1);

    1
    2
    3

        (2)public final synchronized void setElementAt(Object obj,int index)
        将index处的对象设置成obj,原来的对象将被覆盖。
        (3)public final synchronized void insertElement(Object obj,int index)
        在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。
        (4)public final synchronized void removeElement(Object obj)
        从向量中删除obj,若有多个存在,则从向量头开始试,删除找到的第一个与obj相同的向量成员。
        (5)public final synchronized void removeAllElement();
        删除向量所有的对象
        (6)public fianl synchronized void removeElementAt(int index)
        删除index所指的地方的对象
        (7)public final int indexOf(Object obj)
        从向量头开始搜索obj,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1.
        (8)public final synchronized int indexOf(Object obj,int index)
        从index所表示的下标处开始搜索obj.
        (9)public final int lastindexOf(Object obj)
        从向量尾部开始逆向搜索obj.
        (10)public final synchornized int lastIndex(Object obj,int index)
        从index所表示的下标处由尾至头逆向搜索obj.
        (11)public final synchornized firstElement()
        获取向量对象中的首个obj
        (12)public final synchornized Object lastElement()
        获取向量对象的最后一个obj
        (13)public final int size();
        此方法用于获取向量元素的个数。它们返回值是向量中实际存在的元素个数,而非向量容量。可以调用方法capacity()来获取容量值。
        (14)public final synchronized void setsize(int newsize);
        此方法用来定义向量的大小,若向量对象现有成员个数已经超过了newsize的值,则超过部分的多余元素会丢失。
        (15)public final synchronized Enumeration elements();
        此方法将向量对象对应到一个枚举类型。java.util包中的其他类中也都有这类方法,以便于用户获取对应的枚举类型。
        (Enumeration类的一个对象Enumeration是java.util中的一个接口类,
在Enumeration中封装了有关枚举数据集合的方法。
在Enumeration提供了方法hasMoreElement()来判断集合中是否还有其他元素和方法nextElement()来判断集合中是否还有其他元素和方法nextElement()来获取下一个元素。利用这两个方法,可以依次获得集合中的元素。)


2.Vector线程安全与ArrayList的非线程安全


2.1 线程安全与非线程安全

        线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
2.2 Vector内部如何实现线程安全

public class Vector
{
     Object[] elementData;       // 存放元素的数组
     int elementCount;           // 存放元素的实际数量,默认的容量(capacity)是10
     int capacityIncrement;      // 当容量占满时,扩容量,如果未指定,则原先的2倍(doubled)
     // 构造函数
     public Vector(int initialCapacity/* 初始容量 */,int capacityIncrement/*扩容量*/){}
}

Vector类中的capacity()/size()/isEmpty()/indexOf()/lastIndexOf()/removeElement()/addElement() 等方法均是 sychronized 的,所以,对Vector的操作均是线程安全的。
       对于Vector的操作均是线程安全这句话还需要注意一点是:如果是单个方法进行调用是线程安全的,但是如果是组合的方式进行调用则需要再次进行同步处理,例如:

if (!vector.contains(element))
    vector.add(element);
    ...
}

       这是经典的 put-if-absent 情况,尽管 contains, add 方法都正确地同步了,但作为 vector 之外的使用环境,仍然存在 race condition: 因为虽然条件判断 if (!vector.contains(element))与方法调用 vector.add(element); 都是原子性的操作 (atomic),但在 if 条件判断为真后,那个用来访问vector.contains 方法的锁已经释放,在即将的 vector.add 方法调用之间有间隙,在多线程环境中,完全有可能被其他线程获得 vector的 lock 并改变其状态, 此时当前线程的vector.add(element); 正在等待(只不过我们不知道而已)。只有当其他线程释放了 vector 的 lock 后,vector.add(element); 继续,但此时它已经基于一个错误的假设了。
       单个的方法 synchronized 了并不代表组合(compound)的方法调用具有原子性,使 compound actions 成为线程安全的可能解决办法之一还是离不开intrinsic lock (这个锁应该是 vector 的,但由 client 维护):

// Vector v = ...
    public  boolean putIfAbsent(E x) {
synchronized(v) {
            boolean absent = !contains(x);
            if (absent) {
                add(x);
}
}
        return absent;
    }

 

       所以在回答Vector与ArrayList的区别时,应该这样回答:
Vector 和 ArrayList 实现了同一接口 List, 但所有的 Vector 的方法都具有 synchronized 关键修饰。但对于复合操作,Vector 仍然需要进行同步处理。


2.2 为什么ArrayList线程不安全?

举个栗子:
       一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:

    在 Items[Size] 的位置存放此元素;
    增大 Size 的值。 增大 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是非线程安全的,要想实现线程安全的ArrayList,可在ArrayList的基础上通过同步块来实现,或者使用同步包装器(Collections.synchronizedList),还可以使用J.U.C中的CopyOnWriteArrayList。
       ArrayList的非线程安全带来的问题:

final ArrayList<String> list = new ArrayList<String>(); // 多线程共享的ArrayList
for(int i=0;i<100;i++) // 多个线程同时进行写操作
{
     new Thread(new Runnable(){
        @Override
        public void run() {
        for(int j=0;j<1000;j++)
        {
             list.add("hello"); // 多线程下,此处引发ArrayIndexOutOfBoundsException
       }
    }}).start();
}

 

ArrayList的内部原理:

public class ArrayList<E>
{
    private Object[] elementData;      // 存储元素的数组。其分配的空间长度是capacity。
    private int size;                  // elementData存储了多少个元素。
    public ArrayList(){this(10);};     // 默认capacity是10
    boolean add(E e)
    {
         ensureCapacityInternal(size + 1);  // capacity至少为 size+1
         elementsData[size++]=e;            // size++
         return true;
    }
    void ensureCapacityInternal(int minCapacity){
         if(minCapacity > elementData.length)     // 扩容
              grow(minCapacity);
    }
    void grow(int minCapacity){
         int oldCapacity = elementData.length;
         int newCapacity = oldCapacity + (oldCapacity >> 1);     // 约是原先的1.5倍。
         elementData = Arrays.copyOf(elementData,newCapacity );
    }
}

   

如何实现线程安全的ArrayList

    #1:自己手动同步
    public static List<E> list = ... ;
    lock.lock();
    list.add();
    lock.unlock();
    #2:使用同步包装器
    List<E> syncList = Collections.synchronizedList(new ArrayList<E>());
    迭代时,需要包含在同步块当中
    synchronized(syncList){
        while(Iterator<E> iter = syncList.iterator();iter.hasNext();){}
    }
    #3:使用J.U.C中的CopyOnWriteArrayList。

 ————————————————
版权声明:本文为CSDN博主「MyKelly_2013」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43407007/article/details/87901795

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值