美文网首页Java 杂谈
小白速学Java系列:Java基础之CopyOnWriteArr

小白速学Java系列:Java基础之CopyOnWriteArr

作者: Web前端学习营 | 来源:发表于2019-04-26 19:57 被阅读0次

    前言

    今天我们来学习一下CopyOnWriteArrayList这个比ArrayList更安全的集合,同时带着以下几个问题去分析源码。

    CopyOnWriteArrayList如何保证线程安全?

    CopyOnWriteArrayList有什么优缺点吗?

    CopyOnWriteArrayList与ArrayList有什么区别吗?

    了解CopyOnWriteArraySet吗?

    哦对了,在分享这篇文字前,我先说一下,我这里有一份Java学习资料,直接加我的Java直播学习群:1004944760就能免费领取,长期真实有效。

    继承关系

    publicclassCopyOnWriteArrayListimplementsList,RandomAccess,Cloneable,java.io.Serializable{//......}复制代码

    首先我们来看一下CopyOnWriteArrayList的继承关系,从源码中可以看出它分别实现了List、RandomAccess、Cloneable以及Serializable,对于我们比较熟悉的应该是List和Serializable了。

    核心字段

    //可以看出这是重入锁finaltransientReentrantLock lock =newReentrantLock();//底层数组,可以看出用了volatileprivatetransientvolatileObject[] array;  复制代码

    通过以上两个核心字段,可以猜出CopyOnWriteArrayList内部进行了加锁来保证线程安全。那么它到底是如何实现的呢?我们继续往下分析。

    构造方法

    /**

    * 默认构造方法,初始化了一个空数组

    */publicCopyOnWriteArrayList(){      setArray(newObject[0]);  }复制代码

    再来简单看一下其他两个构造方法,如下:

    publicCopyOnWriteArrayList(Collectionc) {Object[] elements;if(c.getClass() ==CopyOnWriteArrayList.class)            elements = ((CopyOnWriteArrayList)c).getArray();else{            elements =c.toArray();// c.toArray might (incorrectly) not return Object[] (see 6260652)if(elements.getClass() !=Object[].class)                elements =Arrays.copyOf(elements, elements.length,Object[].class);        }        setArray(elements);    }publicCopyOnWriteArrayList(E[] toCopyIn) {      setArray(Arrays.copyOf(toCopyIn, toCopyIn.length,Object[].class));  }复制代码

    修改

    publicEset(intindex, E element){//进行加锁final ReentrantLocklock=this.lock;lock.lock();try{//获取旧数组Object[] elements = getArray();//获取旧数据E oldValue =get(elements, index);//如果旧数据与插入的新数据不同if(oldValue != element) {intlen = elements.length;//创建了一个新数组Object[] newElements = Arrays.copyOf(elements, len);              newElements[index] = element;//设置array为newElementssetArray(newElements);          }else{// Not quite a no-op; ensures volatile write semanticssetArray(elements);          }returnoldValue;      }finally{//解锁lock.unlock();      }  }复制代码

    可以看出在写入数据时通过加锁来保证线程安全,并且通过复制一个新数组。然后修改新数组中index对应的Value值,最后将新数组赋值给CopyOnWriteArrayList的底层数组array,也可以说是将array数组指向新数组,如下图:

    对于CopyOnWriteArrayList的set()方法,我们可以这样理解。

    获取旧数组

    通过复制旧数组创建新数组

    在新数组中进行修改

    array指向新数组

    添加

    publicbooleanadd(E e){      final ReentrantLocklock=this.lock;lock.lock();try{          Object[] elements = getArray();intlen = elements.length;          Object[] newElements = Arrays.copyOf(elements, len +1);          newElements[len] = e;          setArray(newElements);returntrue;      }finally{lock.unlock();        }    }复制代码

    可以看出与set()方法类似,也是通过加锁的方式来保证线程安全。然后创建一个新数组来添加元素,最后将新数组赋值给array。

    获取

    publicEget(int index) {returnget(getArray(), index);    }@SuppressWarnings("unchecked")privateEget(Object[] a, int index) {return(E) a[index];    }复制代码

    从get()方法源码中可以看出 ,在从CopyOnWriteArrayList获取元素时并没有进行加锁。那么我们就要思考一个问题了,如果当写入数据时,在没有写完数据的情况下。再去读取数据,那么我们能准确的获得数据吗?而且重点是在get()方法中,是直接对底层数组array进行操作的。那就意味着在我们读取数据时,有可能获得的是旧数据。也就是说CopyOnWriteArrayList并不能保证数据的实时性,主要原因可以从以下几点来思考。

    在get()方法并没有进行加锁

    读取的是底层数组array的数据

    删除

    publicEremove(intindex){//加锁final ReentrantLocklock=this.lock;lock.lock();try{            Object[] elements = getArray();intlen = elements.length;            E oldValue =get(elements, index);intnumMoved = len - index -1;if(numMoved ==0)                setArray(Arrays.copyOf(elements, len -1));else{                Object[] newElements =newObject[len -1];                System.arraycopy(elements,0, newElements,0, index);                System.arraycopy(elements, index +1, newElements, index,                                numMoved);                setArray(newElements);            }returnoldValue;        }finally{//解锁lock.unlock();        }    }复制代码

    可以看出remove()方法也是一样的,通过加锁来保证线程安全。

    其他方法

    /**

    * 获取底层数组

    */final Object[] getArray() {returnarray;    }/**

    * 设置底层数组

    */finalvoidsetArray(Object[] a){array= a;    }/**

    * 获取数组大小

    */publicintsize(){returngetArray().length;    }/**

    * 判断数组是否为空

    */publicbooleanisEmpty(){returnsize() ==0;    }复制代码

    优点与缺点

    通过查看源码,我们可以看出CopyOnWriteArrayList最大的优点就是线程安全,也是最突出的优点。它在读取操作时没有加锁,只有当进行写入操作时才会加锁。这也体现出了CopyOnWriteArrayList的一个缺点,那就是无法读取实时数据。因为可能当写操作还没有完成时,就进行了读取操作,那么读取的将是旧数据。

    CopyOnWriteArraySet

    接下来我们来简单了解一下CopyOnWriteArrayList的表兄弟CopyOnWriteArraySet,主要源码如下。

    privatefinalCopyOnWriteArrayList al;publicCopyOnWriteArraySet(){        al =newCopyOnWriteArrayList();    }复制代码

    通过构造方法就可以看出初始化了一个CopyOnWriteArrayList,也就是说CopyOnWriteArraySet内部是用CopyOnWriteArrayList来实现的,我们再来看一下其他几个方法。

    publicbooleanadd(E e){returnal.addIfAbsent(e);    }publicbooleanremove(Object o){returnal.remove(o);    }复制代码

    可以看出add()和remove()都是通过CopyOnWriteArrayList来实现。

    总结

    通过分析源码,我们了解了CopyOnWriteArrayList的内部实现,以及它是如何保证线程安全的,还了解一下CopyOnWriteArraySet。

    相关文章

      网友评论

        本文标题:小白速学Java系列:Java基础之CopyOnWriteArr

        本文链接:https://www.haomeiwen.com/subject/pwzqnqtx.html