多线程辅助类之数组(1)

什么是CopyOnWrite容器

     CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

      从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

CopyOnWriteArrayList的实现原理

      在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。以下代码是向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList里添加元素),可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。

    查询API文档,我们发现以下两个雷还是有很多相似的地方的:

public class CopyOnWriteArrayList<E>extends Objectimplements List<E>, RandomAccess, Cloneable, Serializable
public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable

    《Java多线程辅助类之数组(一)》中我们分析了ArrayList类的add(E e)方法,发现从头到尾没有发现线程锁,而CopyOnWriteArrayList的add(E e)居然有锁,方法源码如下:

/**
     * 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) {
        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();
        }
    }

再比较一下读取数据,ArrayList的读取 方法:

 /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

CopyOnWriteArrayList的读取方法:

 /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }


通过比较我们发现CopyOnWriteArrayList和ArrayList的读取方法我们发现,读的时候不需要加锁。如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

下面看一段代码:

package syn;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

//面试题   
public class Test extends Thread{
    
    private TestDo testDo;
    private String key;
    private String value;
    
    public Test(String key,String key2,String value){
        this.testDo = TestDo.getInstance();
        /*常量“1”和“1”是同一个对象,下面这行代码就是要用1+“”的方式产生新的对象以实现
           内容没有改变,任然相等(都是1),但对象却不再是同一个效果*/
        this.key = key+key2; 
/*        a = "1"+"";
        b = "1"+""
*/
        this.value = value;
    }


    public static void main(String[] args) throws InterruptedException{
        Test a = new Test("1","","1");
        Test b = new Test("1","","2");
        Test c = new Test("3","","3");
        Test d = new Test("4","","4");
        System.out.println("begin:"+(System.currentTimeMillis()/1000));
        a.start();
        b.start();
        c.start();
        d.start();

    }
    
    public void run(){
        testDo.doSome(key, value);
    }
}

class TestDo {

    private TestDo() {}
    private static TestDo _instance = new TestDo();    
    public static TestDo getInstance() {
        return _instance;
    }

    //private ArrayList keys = new ArrayList();
    private CopyOnWriteArrayList keys = new CopyOnWriteArrayList();
    public void doSome(Object key, String value) {
        Object o = key;
        if(!keys.contains(o)){
            keys.add(o);
        }else{

            for(Iterator iter=keys.iterator();iter.hasNext();){
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Object oo = iter.next();
                if(oo.equals(o)){
                    o = oo;
                    break;
                }
            }
        }
        synchronized(o)
        // 以下大括号内的是需要局部同步的代码,不能改动
        {
            try {
                Thread.sleep(1000);
                System.out.println(key+":"+value + ":"
                        + (System.currentTimeMillis() / 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

使用这个数组最大的好处就是,不用担心读、写操作在某一时间点同时完成,ArrayList则会报错

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值