您好!欢迎来到爱源码

爱源码

热门搜索: 抖音快手短视频下载   

深入分析CopyOnWriteArrayList,线程安全数组列表 {互站网}

  • 时间:2022-08-14 00:42 编辑: 来源: 阅读:274
  • 扫一扫,手机访问
摘要:深入分析CopyOnWriteArrayList,线程安全数组列表 {互站网}
前言ArrayList是线程不安全的,这一点毋庸置疑。 因为ArrayList的所有方法既没有锁,也没有额外的线程安全解决方案。 Vector作为线程安全版的ArrayList,总是存在感很低。 因为所有的添加、移除或获取方法都有同步锁,所以效率很低。 在JDK1.5引入的J.U.C包中,实现了线程安全版本的ArrayList-copyonWriteArrayList。 成员变量,我们先来看看CopyOnWriteArrayList类的定义和底层数据结构公共类copyonwritearraylist < E & gt实现列表& ltE & gt、RandomAccess、Cloneable、Java . io . serializable { private static final long serial version uid = 8673264195747942595 l;/**保护所有赋值函数的锁*/transient final reentrant lock lock = new reentrant lock();/* *只能通过getarray/setarray访问的数组。*//存储数据的数组数组。注意,这是一个用volatile修饰的私有volatile transient object []数组;}根据定义,ReentrantLock成员变量比ArrayList多一个。存储数据的数组用volatile修饰,其他的差别不大。 存储的数据结构仍然是一个数组。 方法/***设置数组。*语法sugar */final void set array(object[]a){ array = a;}/***创建一个空列表。*/public CopyOnWriteArrayList(){ set array(new Object[0]);}/* * *创建一个保存给定数组副本的列表。*创建一个列表保存给定数组的副本(将参数给定的数组复制到成员变量)* * @ throws nullpointerexception如果指定数组为null *参数数组为null,Throw NullPointeException */public copyonWriteArrayList(e[]to copy in){ set array(arrays . copy of(to copy in,tocopyin.length,object []。类));}看了构造方法,还是有些疑惑。成员变量和构造方法看起来比ArrayList简单。你如何保证线程安全? 也许add方法会给我们答案。 核心方法add(E e)add(E e)方法用于将元素添加到列表的末尾,CopyOnWriteArrayList中add(E e)方法的源代码如下:/* *将指定的元素追加到这个列表的末尾。*将指定的元素添加到列表的末尾* * @ param e要追加到列表中的元素* @ return < TT & gt;true & lt/TT & gt;(由{ @ link Collection # add })*/public boolean add(E E){ final reentrant lock lock = this . lock;//lock lock . lock();Try {// Get成员变量array[]object[]elements = Get array();int len = elements.length//将原数组复制到新数组中(即将增加一个元素,所以len+1)object[]new elements = arrays . Copy of(elements,len+1);new elements[len]= e;//新数组替换原数组set array(new elements);返回true}最后{//unlock lock . unlock();}}从这段代码中可以得到以下信息:add方法保证最多只有一个线程可以通过ReentrantLock同时向列表中添加元素。它必须是线程安全的。它不是直接向数组中添加元素,而是开发一个新数组,将元素插入到新数组中,然后用新数组替换旧数组。既然ReentrantLock已经保证了线程安全,为什么还需要开发新的数组?因为当数组被volatile修饰时,只能保证数组的引用有volatile语义。 也就是说,一个易变的修饰数组,即使数组中的元素发生变化,也不会触发可见性。 处理这个问题有两种方法:使用AtomicIntegerArray或AtomicLongArray修改数组的内存地址,即重新分配数组。除了易变语义的问题之外,get方法还有一个原因,这将在下面详细描述。 add (int index,ee element)add(int index,ee element)方法用于将元素添加到列表的指定位置。源代码如下:/* * *在指定位置添加元素* * @ Throws IndexoutofBoundsException { @ inherit doc } */Public Void Add(int index,ee element){ final reentrant lock = this . lock;//lock lock . lock();Try {// Get原数组对象[]elements = Get array();int len = elements.lengthif(index & gt;len | | index & lt0)抛出新的IndexOutOfBoundsException(" Index:"+Index+",Size:"+len);object[]new elements;//计算要移动的元素个数int num moved = len-index;If (numMoved == 0) //最后添加新元素= arrays.copyof (elements,len+1);Else {//开发新数组new elements = new Object[len+1];//将索引前的元素复制到新数组中。在复制前后,元素的索引保持不变。系统。ArrayCopy(元素,0,新元素,0,索引);//将索引后的元素复制到新数组中。复制后,下标+1 //保留给新添加的元素系统。ArrayCopy (elements,index,new elements,index+1,nummoved)因为需要空出新数组的索引;} new elements[index]= element;//新数组替换原数组set array(new elements);}最后{//unlock lock . unlock();}}}这两种add方法功能不同,但实现步骤和原理相似。它们一般都可以分为五个步骤:1 .2号锁。开发新的阵列3。复制元素4。用新阵列替换旧阵列5。解锁CopyOnWriteArrayList。虽然底层也是数组实现,但是没有所谓的扩展。 因为每次添加都会打开一个新数组 而且每次添加都会被锁定,所以效率比较低。 remove(int index)remove(int index)方法用于删除和返回指定位置的元素。其源代码如下:/* *删除并返回指定位置的元素* * @ Throws IndexoutofBoundsException { @ inherit doc } */public eremove(int index){ final reentrant lock = this . lock;//lock lock . lock();Try {// Get原数组对象[]elements = Get array();int len = elements.length//获取指定位置的值,用于返回E oldValue = get(elements,index);//要移动的元素个数int num moved = len-index-1;If (numMoved == 0) //只删除尾部元素set array(arrays . copy of(elements,len-1));else {//Develop a new array object[]new elements = new object[len-1];//将index之前的元素复制到新数组中,复制前后的索引不变。系统。ArrayCopy(元素,0,新元素,0,索引);//将index后的元素复制到新数组,然后下标-1system.arraycopy (elements,index+1,new elements,index,num moved);//新数组替换原数组set array(new elements);}//返回删除的值返回oldValue}最后{//unlock lock . unlock();}}从源代码可以看出,无论是add还是remove。 都是通过reentrant lock+volatile+array copy实现线程安全的。 写到这里,也没看出CopyOnWriteArrayList比Vector更高效的地方。除此之外,前者每次添加/删除操作都会打开一个新数组,相当于浪费了两倍的空间。 然后,接下来就是见证奇异了……咳咳,没有奇迹。我们来看看CopyOnWriteArrayList的优点。 vector效率低,在get中加入了同步锁,但是CopyOnWriteArrayList的get方法不需要锁get(int index)get(int index)方法来获取指定位置的元素。源代码如下:/* * * * { @ inherit doc } * * @ throws indexoutofboundsexception { @ inherit doc } */public eget(intindex){//调用内部get方法return get(getArray(),index);}@SuppressWarnings("未检查")private E get(Object[] a,int index){ return(E)a[index];}可以看到get(int index)不需要锁。因为CopyOnWriteArrayList在添加/移除操作期间不修改原始数组,所以在读取操作中不会有线程安全问题。 其实这就是读写分离的思想。只有写的时候加锁,复制原件修改。 CopyOnWriteArrayList也称为写入时复制容器。 而且在迭代过程中,即使数组的结构发生变化,也不会抛出ConcurrentModificationException异常。 因为迭代总是原始数组,并且所有更改都发生在原始数组的副本上。 所以对于迭代器来说,迭代的集合结构是不会变的。 优缺点CopyOnWriteArrayList主要有两个优点:线程安全大大提高了“读”操作的并发性(与Vector相比),缺点也很明显:每次“写”操作都会开辟一个新的数组,浪费空间无法保证时效性。因为“读”和“写”不在同一个数组中,而且“读”操作没有互斥锁,所以无法保证强一致性。只能保证最后的一致性。添加/删除操作效率低下,需要锁定和复制数组。因此,CopyOnWriteArrayList更适合读得多写得少的场景。 注意:不要在循环中添加/删除CopyOnWriteArrayList。CopyOnWriteArrayList提供了相应的批处理解决方案addAll和removeAll。 以下是循环中add操作和addAll操作的比较:/* * loop+add vs addAll */public class copyonwritearraylistdemo { private static final int count = 100000;私有静态最终列表& lt整数& gtlist 1 = new CopyOnWriteArrayList & lt;& gt();私有静态最终列表& lt整数& gtlist 2 = new CopyOnWriteArrayList & lt;& gt();公共静态void main(String[]args){ List & lt;整数& gtdataList = new ArrayList & lt& gt(计数);for(int I = 0;我& lt数数;i++){ datalist . add(I);} testCopyOnWriteArrayList(dataList);}私有静态void testCopyOnWriteArrayList(List & lt;整数& gtdataList){ long time 1 = system . current time millis();for(整数数据:dataList) { list1.add(数据);} long time 2 = system . current time millis();System.out.println ("Loop +add耗时:"+(time2-time1)/1000.0+"秒");list 2 . addall(dataList);long time 3 = system . current time millis();System.out.println("addAll耗时:"+(time3-time2)/1000.0+"秒");}}}执行结果循环+add花费的时间:2.604秒。花在addAll上的时间:0.001秒。这是一种直观的方式,可以看出两者的效率差异。 总结CopyOnWriteArrayList使用reentrant lock+volatile+array copy实现线程安全的ArrayList。 在特定场景下使用CopyOnWriteArrayList,既能保证线程安全,又有很好的体现。 作者:Sicimike参考链接:https://blog.csdn.net/Baisitao_/article/details/103377409


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【域名/主机/服务器|】qq邮箱提醒在哪里打开(2024-06-04 18:58)
【技术支持|常见问题】1556原创ng8文章搜索页面不齐(2024-05-01 14:43)
【技术支持|常见问题】1502企业站群-多域名跳转-多模板切换(2024-04-09 12:19)
【技术支持|常见问题】1126完美滑屏版视频只能显示10个(2024-03-29 13:37)
【技术支持|常见问题】响应式自适应代码(2024-03-24 14:23)
【技术支持|常见问题】1126完美滑屏版百度未授权使用地图api怎么办(2024-03-15 07:21)
【技术支持|常见问题】如何集成阿里通信短信接口(2024-02-19 21:48)
【技术支持|常见问题】算命网微信支付宝产品名称年份在哪修改?风水姻缘合婚配对_公司起名占卜八字算命算财运查吉凶源码(2024-01-07 12:27)
【域名/主机/服务器|】帝国CMS安装(2023-08-20 11:31)
【技术支持|常见问题】通过HTTPs测试Mozilla DNS {免费源码}(2022-11-04 10:37)

联系我们
Q Q:375457086
Q Q:526665408
电话:0755-84666665
微信:15999668636
联系客服
企业客服1 企业客服2 联系客服
86-755-84666665
手机版
手机版
扫一扫进手机版
返回顶部