首页 > 编程知识 正文

copyofrange方法,arraylist有顺序吗

时间:2023-05-04 18:47:16 阅读:165146 作者:1726

前言CopyOnWriteArrayList这是ArrayList的线程安全变体。 恐怕在初始化时只有一个容器,在长时间该容器的数据、数量等没有变化的情况下,大家(几乎所有的线程)读取同一容器内的数据(假设在该时间只发生读取操作) 这样,大家读取数据就变得唯一了。 但是,后来有人在里面添加了数据。 此时,CopyOnWriteArrayList的基础实现附加原理是,首先copy提交容器(副本),然后将新数据添加到新容器中,最后将新容器的引用地址指定给以前的旧容器地址但是,在添加此数据的同时,其他线程在读取数据时仍然会读取旧容器中的数据。

由于测试用例List没有线程安全,所以并发读取/写入将失败。 测试demo如下。

package com.lyj.demo.custom tests; import org.junit.Test; import org.junit.runner.RunWith; importorg.spring framework.test.context.JUnit4. spring JUnit4class runner; import java.util.ArrayList; import java.util.List; import Java.util.concurrent.executorservice; import Java.util.concurrent.executors; /** * @author雅致西装* @date 2021/4/27 9:32 * List集合测试*/@ run with (spring JUnit4class runner.class ) publicclass //支持并发//privatestaticliststringmlist=newcopyonwritearraylist (; /** * List读写不支持同时测试。 */@ testpublicvoidcopyonwritearraylisttest ({ start test ); } /** *初始化数据*/private void initList () ) for(intI=0; i 10; I ) {mlist.add(line: () I1 ) ) data ); } }私有语音开始测试() /初始化数据initList ); system.out.print ln---------- -初始化完成---------------; executorserviceexecutorservice=executors.newfixedthreadpool (thread _ pool _ max _ size ); //读写同时测试for(intI=0; i THREAD_POOL_MAX_SIZE; I ) )//读取任务立即执行executorservice.execute((-) ) string item 3360 mlist (system.out.println ) thread.currents 最终最终最终最终最终最终最终结果1=I 10; //写入任务立即执行executorService.execute ()-()- { mList.add ) )写入线程附加数据(final1)……); ); }}运行结果:

从执行结果来看,同一个实际上多个线程不能读取或删除同一个List。 否则,会抛出Java.util.concurrentmodificationexception (并发失败),因此必须将(new ArrayList )更改为newcopyonwrition

如果取消注释现场代码,注册并重新启动非线程安全的new ArrayList (),执行结果将不会出错,如下所示:

解析源代码:

从源代码中可以看到,使用任何构造函数都将创建CopyOnWriteArray

List对象,都会创建一个Object类型数组,然后赋值给成员array。
注意,这里有个transient关键字,它的作用在对象需要进行序列化时,对对标有transient的变量不被序列化到本地。volatile是保证原子性。
CopyOnWriteArrayList处理并发问题,同样是使用锁。

/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (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(); } } /** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). Returns the element that was removed from the list. * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { lock.unlock(); } }

从源码上看,添加和删除元素,都是使用了两行代码进行加锁和解锁,保证同一时间只有一个线程在添加和删除元素。然后在添加使用Arrays.copyOf()方法复制出另一个新的数组,而且新的数组长度比原来的数组长度+1,副本复制完毕,新添加的元素也赋值添加完毕,最后又把新的副本数组赋值给旧的数组,最后在finally代码块将锁释放。
删除元素,就是判断要删除的元素是否是最后一个,如果最后一个直接在复制副本数组的时候,复制长度为旧数组的length-1即可;但是如果不是最后一个元素,就先复制旧数组的index前面元素到新数组中,然后再复制旧数组中index后面的元素到数组中,最后再把新数组复制给旧数组引用。
CopyOnWriteArrayList其他增删改逻辑大致类似。都是使用阻塞锁。
CopyOnWriteArrayList读取数据是不需要加锁的。

CopyOnWriteArrayList优缺点 优点:解决开发工作中的多线程并发问题缺点:
1. 内存占用问题:很明显,两个数组同时驻扎在内存中,如果实际应用中,数据比较多,而且比较大的情况下,占用内存会比较大,针对这个其实可以使用ConcurrentHashMap来代替。
2. 数据一致性:CopyOnWriteArrayList容器只能保证数据的最终已执行,不能保证数据的实时一致性,所以如果希望写入的数据,马上能读取到,就不能使用CopyOnWriteArrayList。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。