ArrayList用的太多了,几乎所有人都知道它是线程不安全的,但实际使用中,我们的多线程实现,普遍都是基于一些同步方法或者锁,很多场景其实并不需要关注ArrayList本身的线程安全。网上可以找到三种主流的实现ArrayList线程安全的手段,他们分别是什么样的思路,还是值得简单了解和记录的。

Vector

Vector 是矢量队列,它是JDK1.0版本添加的类,历史比ArrayList(since 1.2)更为悠久。其具体历史已不太可考,个人只能简单猜测是在java初期从其他语言直接借鉴的名字及相关概念。其底层和ArrayList一样是数组,除线程安全外,大多数实现方法和ArrayList逻辑基本一致。值得一提的是,从jdk1.2以后,Java提供了系统的集合框架,就将Vector改为实现List接口,从而导致Vector里有一些重复的方法,例如:addElement(Object obj),实际上这个方法和add(Object obj)没什么区别。

Vector 的实现,就是在方法上都加上synchronized(即使get也不例外)。康康部分源码。

public synchronized E get(int index) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);return elementData(index);
}public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}

关于Vector为什么被弃用

  1. 所有方法都有同步开销,非多线程下,效率不如ArrayList;
  2. 一些老代码,导致有重复的方法,以及风格和新的集合类格格不入;
  3. 线程安全的实现,可以通过新的Collections.synchronizedList之类的调用来替换。

Collections.synchronizedList

基于集合类的实现,可以简单的使用函数来操作,例如,List<Integer> list2 = Collections.synchronizedList(new ArrayList<Integer>())。先看看函数的定义。

public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new Collections.SynchronizedRandomAccessList<>(list) :new Collections.SynchronizedList<>(list));
}

显然,里面还藏了一些list类,我们以SynchronizedRandomAccessList接着看看这个类是如何操作的。

//Collections中的静态类1号,主要实现在SynchronizedList中
static class SynchronizedRandomAccessList<E>extends Collections.SynchronizedList<E>implements RandomAccess {... //绝大部分方法都没有单独的实现
}
//Collections中的静态类2号
static class SynchronizedList<E>extends Collections.SynchronizedCollection<E>implements List<E> {private static final long serialVersionUID = -7754090372962971524L;final List<E> list;...//可以看到,大部分方法的处理方式,是多了一个synchronized (mutex),//mutex是SynchronizedCollection中的一个Object变量public boolean equals(Object o) {if (this == o)return true;synchronized (mutex) {return list.equals(o);}}public int hashCode() {synchronized (mutex) {return list.hashCode();}}public E get(int index) {synchronized (mutex) {return list.get(index);}}public E set(int index, E element) {synchronized (mutex) {return list.set(index, element);}}public void add(int index, E element) {synchronized (mutex) {list.add(index, element);}}public E remove(int index) {synchronized (mutex) {return list.remove(index);}}public int indexOf(Object o) {synchronized (mutex) {return list.indexOf(o);}}public int lastIndexOf(Object o) {synchronized (mutex) {return list.lastIndexOf(o);}}public boolean addAll(int index, Collection<? extends E> c) {synchronized (mutex) {return list.addAll(index, c);}}...//官方注释明确提出,对于使用 Iterator遍历列表时,Collections.synchronizedList可能发生错误//还需要手动去确保线程安全public ListIterator<E> listIterator() {return list.listIterator(); // Must be manually synched by user}public ListIterator<E> listIterator(int index) {return list.listIterator(index); // Must be manually synched by user}
}

CopyOnWriteArrayList

CopyOnWriteArrayList是1.5后引入,属于JUC的一部分。他基本的原理还是和ArrayList一样,涉及线程安全的部分,是通过写时复制的方式来实现(从名字中就可以看出)。它内部有个volatile数组来保持数据。在“添加/修改/删除”数据时,会先获取互斥锁,再新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给volatile数组,然后再释放互斥锁。以get和set为例看看代码。

public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {private static final long serialVersionUID = 8673264195747942595L;//互斥锁final transient ReentrantLock lock = new ReentrantLock();//存储数据的volatile数组,入口仅限于类中函数getArray/setArray。private transient volatile Object[] array;public E get(int index) {return get(getArray(), index);}public E set(int index, E element) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();E oldValue = get(elements, index);if (oldValue != element) {int len = elements.length;//直接开始复制一格数组,修改某个值,将型数组赋值给CopyOnWriteArrayList中的arrayObject[] newElements = Arrays.copyOf(elements, len);newElements[index] = element;setArray(newElements);} else {// Not quite a no-op; ensures volatile write semantics// 官方介绍为,并非无效的操作,是为了保证volatile的写语义。// 这里有点搞心态,翻译一下参考文档6,按照其中一个回答,这里的代码jdk 8中存在,jdk 11中已经移除。但我在jdk 14中又再次发现。// 参考热门回答,主要是为了确保一些逻辑中CopyOnWriteArrayList外的非 volatile变量,由于指令重排导致的执行顺序问题。setArray(elements);}return oldValue;} finally {lock.unlock();}}
}

因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。

性能对比

我尝试的代码主要来自参考文档4,就不再贴出来了。

CopyOnWriteArrayList add method cost time is 17156
Collections.synchronizedList add method cost time is 23
Vector add method cost time is 5
CopyOnWriteArrayList get method cost time is 2
Collections.synchronizedList get method cost time is 4
Vector get method cost time is 5
---- 10万级数据CopyOnWriteArrayList add method cost time is 133
Collections.synchronizedList add method cost time is 1
Vector add method cost time is 0
CopyOnWriteArrayList get method cost time is 0
Collections.synchronizedList get method cost time is 0
Vector get method cost time is 1
---- 万级数据

以上代码又多跑了几次,大同小异。

结论:

  1. 写时CopyOnWriteArrayList性能较差,且随着数据量的增大,几何级下跌。读操作各方式基本没有区别。
  2. CopyOnWriteArrayList,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。
  3. Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用,但是有需要同步的地方使用, 比如读写操作都比较均匀的地方。
  4. 不得不含泪承认,从简单的几次跑数中,Vector的读写都很优秀。但既然已经不建议使用,就忘了它吧。

参考文档

1. Java集合(五)应该弃用的Vector和Stack,Java集合(五)应该弃用的Vector和Stack_weixin_33695082的博客-CSDN博客

2. vector过时的代替建议,vector过时的代替建议_忧伤的可乐鸡-CSDN博客_vector的替代

3. 简单理解Collections.synchronizedList,简单理解Collections.synchronizedList_walker-CSDN博客_collections.synchronizedlist

4. CopyOnWriteArrayList与Collections.synchronizedList的性能对比,https://blog.csdn.net/yangzl2008/article/details/39456817

5. Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList,Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList - 如果天空不死 - 博客园

6. Why setArray() method call required in CopyOnWriteArrayList,java - Why setArray() method call required in CopyOnWriteArrayList - Stack Overflow

如何实现ArrayList的线程安全相关推荐

  1. 验证ArrayList是线程不安全的集合

    package collectionSafe; import java.util.ArrayList;import java.util.Collections;import java.util.Lis ...

  2. 多线程下ArrayList类线程不安全的解决方法及原理

    多线程下ArrayList类线程不安全的解决方法及原理 参考文章: (1)多线程下ArrayList类线程不安全的解决方法及原理 (2)https://www.cnblogs.com/fangting ...

  3. ArrayList为何线程不安全,如何解决

    我们知道ArrayList是线程不安全的,与之对应的线程安全Vector,为何?看源码 ArrayList: public boolean add(E e) {ensureCapacityIntern ...

  4. java证明ArrayList是线程不安全的

    证明ArrayList是线程不安全的 我们开启100个线程.每个线程向List加100个数据,那么当所有线程执行完成之后应该是10000条,然后就对比一下结果,看看是否为10000条. thread. ...

  5. Java面试之ArrayList为什么线程不安全?

    Collection线程不安全的举例 前言 1.当我们执行下面语句的时候,底层进行了什么操作 new ArrayList<Integer>(); 底层创建了一个空的数组,伴随着初始值为10 ...

  6. java arraylist线程安全_面试题1:ArrayList 是线程安全的吗?如果要实现一个线程安全的List应该怎么做?...

    ZJ面试被问到的问题,我们来一个一个问题看 首先第一个问题,ArrayList是线程安全的吗? 答案是不是,我们可以看看ArrayList的源代码 public E set(int index, E ...

  7. Java之List系列--ArrayList保证线程安全的方法

    原文网址:Java之List系列--ArrayList保证线程安全的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java中的ArrayList.LinkedList如何进行线程安全的操作.为 ...

  8. ArrayList类线程不安全的解决方法

    /*** 描述: ArrayList线程不安全由于add方法没加锁,多线程高并发情况下java.util.ConcurrentModiricationException异常** @author xin ...

  9. 【面试专栏】ArrayList 非线程安全案例并提供三种解决方案

    1. 复现问题 import java.util.ArrayList; import java.util.List; import java.util.UUID;/*** 复现问题* * @autho ...

最新文章

  1. 阿里达摩院数学竞赛考题曝光!4道题限时48小时,网友:题目能看懂但就是不会做...
  2. 几个机器学习算法及应用领域相关的中国大牛
  3. svm 交叉验证 python_【python机器学习笔记】SVM实例:有毒蘑菇预测
  4. python自动发邮件富文本_django 实现后台从富文本提取纯文本
  5. 观察者模式的应用场景
  6. vue商城源码_一个标星 5.2k+ 的牛逼开源商城系统
  7. python随机划分数据集_Python之机器学习-sklearn生成随机数据
  8. spring security:第一个程序解析
  9. 从头认识Spring-1.14 SpEl表达式(1)-简单介绍与嵌入值
  10. 台式网卡计算机,台式机万能网卡驱动,教您如何给台式机安装万能网卡驱动
  11. dllMain函数的作用
  12. 如何将图片压缩到200K以内,有什么好方法吗?
  13. IDEA社区版利用maven创建web
  14. 【Windows 10 更新失败】Windows10 升级提示错误代码:0x80070020解决方案
  15. 支持flv视频播放的h5播放器-xgplayer
  16. 键盘怎么按出计算机,怎么在电脑键盘上打出艾特@键? 原来是这样的
  17. 一个屌丝程序猿的人生(二十七)
  18. 【Pandas实战】足球运动员数据分析
  19. 2018年度盘点 | 隐藏在“信息流”里的那些明争暗斗
  20. 一文带你读懂 Promise

热门文章

  1. NoSQL之Redis配置与数据库常用命令
  2. 群体遗传分析—LD连锁不平衡
  3. ftp服务器型号,ftp服务器的类型及其特点
  4. vmware workstation14永久激活密钥
  5. windows10下Weblogic官网下载与安装教程
  6. 进程、线程、协程、管程
  7. Commvault发布横向扩展一体机 矛头对准Rubrik和Cohesity
  8. 光猫修改上报服务器地址,怎样改光猫的ip地址!急求!!
  9. 小白记录:1、scrapy的基础操作
  10. 【物联网】物联网开发从入门到精通