集合的线程安全性——CopyOnWriteArrayList写时复制

常用的集合ArrayList,LinkedList,HashSet,TreeSet,HashMap,TreeMap等均为线程不安全集合。当运行以下程序时,会报java.util.ConcurrentModificationException异常

public class ContinerNotSafeDemo {
    public static void main(String[] args) {
        List<Integer> list=new ArrayList<>();
        for(int i=0;i<50;i++){
            new Thread(()->{
                list.add(new Random().nextInt());
                System.out.println(list);
            },Thread.currentThread().getName()).start();
        }
    }
}

要想让线程安全,有三种选择:

一、可以用Vector类,

二、使用Collections.synchronizedXXX来获得线程安全的集合对象;

三、CopyOnWriteArrayList。

CopyOnWriteArrayList是JDK1.5加入的,在集合添加元素时对方法加锁,同时采用读写分离的技术,具体看代码:

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

这是CopyOnWriteArrayList中的add方法,第7行进行复制和扩容,扩容长度为1;第8步将扩容后的空间赋为新添加的元素;第9步将指针指向新的空间,完成元素添加。

上一步分析了添加的代码,现在来看看获取的代码:

    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    final void setArray(Object[] a) {
        array = a;
    }

    private transient volatile Object[] array;

上一步中已经调用了setArray方法,这个方法的作用是将旧的数组的指针指向新扩容的数组,也就是新数组上位,旧数组抛弃;这里的get方法是从array数组中按index拿到数据。这其就说明了CopyOnWriteArrayList的读写分离的方式,通过加锁保证线程安全性,读写分离方式保证多个线程在读和写的过程中不会发生异常。

总结:在多线程环境中,如果没有共享变量则线程之间一定是安全的;再细一步,如果共享变量没有写操作,只有读,那这样也是线程安全的,因为不会出现不一致的问题。CopyOnWriteArrayList采用了一种读写分离的思想,这种思想在写的时候加锁,并将集合内部元素复制并扩容一个单位,将新加入的元素写入这个新扩容的单位上;如果此时有读操作,那么读的还是以前未扩容前的数组。扩容完成后,将数组指针指向新的数组,抛弃旧数组。采用读写分离使得读操作变得更加高效。类似还有CopyOnWriteArraySet。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值