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

xiaoxiao2021-02-27  253

什么是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则会报错
转载请注明原文地址: https://www.6miu.com/read-4646.html

最新回复(0)