1.ArrayList 线程不安全验证

验证Demo:

/*** 集合类ArrayList线程不安全验证** @author wangjie* @version V1.0* @date 2019/12/17*/
public class ContainerNotSafe {public static void main(String[] args) {List<String> list = new ArrayList<>();for(int i = 0; i < 30 ; i++){new Thread(() ->{list.add(UUID.randomUUID().toString().substring(0,8));System.out.println(list);},i+"").start();}}
}

运行结果:

java.util.ConcurrentModificationException

这个异常各位可是眼熟?

并发修改异常,以下是JDK1.8 文档截图

1,对象被不同的线程同时修改

2,如果单个线程发出违反对象合同的方法调用序列,则该对象可能会抛出此异常。 例如,如果线程在使用故障快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。

2.解决方案

2.1 换Vector

两者区别:

1.ArrayList是线程不安全的,Vector是线程安全的。

2.两者扩容方式不同。在底层数组容量不足时,ArrayList会将容量扩容为原来的1.5倍。而Vector支持在创建的时候主动声明扩容时增加的容量的大小,通过Vector(int initialCapacity, int capacityIncrement)构造函数实现。如果没有声明,或者capacityIncrement <= 0,那么默认扩容为原来的2倍.

// Vector的扩容方法
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}

验证Demo:

/*** 验证Vector线程安全** @author wangjie* @version V1.0* @date 2019/12/17*/
public class ContainerSafe {public static void main(String[] args) {List<String> list = new Vector<>();for(int i = 0; i < 30 ; i++){new Thread(() ->{list.add(UUID.randomUUID().toString().substring(0,8));System.out.println(list);},i+"").start();}}
}

运行结果:

2.2 用Collections.synchronizedList()

先上验证Demo:

public class ContainerSafe {public static void main(String[] args) {//        List<String> list = new Vector<>();List<String> list = Collections.synchronizedList(new ArrayList<>());for(int i = 0; i < 30 ; i++){new Thread(() ->{list.add(UUID.randomUUID().toString().substring(0,8));System.out.println(list);},i+"").start();}}
}

运行结果:

2.2.1 Vector和Collections.synchronizedList

虽然ArrayList是线程不安全的,但是通过Collections.synchronizedList()方法可以将线程不安全的List转成线程安全的List。但官方文档里,有这么一句话:

If you need synchronization, a Vector will be slightly faster than an ArrayList synchronized with Collections.synchronizedList.

Vector比Collections.synchronizedList快一点点。
下面是我扒的一部分源码:

  public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list) :new SynchronizedList<>(list));}SynchronizedList(List<E> list, Object mutex) {super(list, mutex);this.list = list;}SynchronizedCollection(Collection<E> c, Object mutex) {this.c = Objects.requireNonNull(c);this.mutex = Objects.requireNonNull(mutex);}public boolean add(E e) {synchronized (mutex) {return c.add(e);}}}

从代码中可以看出,SynchronizedList类使用了委托(delegation),实质上存储还是使用了构造时传进来的list,只是将list作为底层存储,对它做了一层包装。正是因为多了一层封装,所以就会比直接操作数据的Vector慢那么一点点。

从上面的代码我们也可以看出来,SynchronizedList的同步,使用的是synchronized代码块对mutex对象加锁,这个mutex对象还能够通过构造函数传进来,也就是说我们可以指定锁定的对象。

而Vector则使用了synchronized方法,同步方法的作用范围是整个方法,所以没办法对同步进行细粒度的控制。而且同步方法加锁的是this对象,没办法控制锁定的对象。这也是vector和SynchronizedList的一个区别。

2.3 使用CopyOnWriteArrayList

什么是CopyOnWrite容器?

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWriteArrayList的实现原理,以下是我扒的CopyOnWriteArrayList的部分源码:

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();}}
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();}}
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;Object[] newElements = Arrays.copyOf(elements, len);newElements[index] = element;setArray(newElements);} else {// Not quite a no-op; ensures volatile write semanticssetArray(elements);}return oldValue;} finally {lock.unlock();}}

读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。

写入和删除时加锁,所以一个线程X读取的时候另一个线程Y可能执行remove操作。remove操作首先要获取独占锁,然后进行写时复制操作,就是复制一份当前的array数组,然后在复制的新数组里面删除元素,删除完成后让array指向这个新的数组。

验证Demo:

public class ContainerSafe {public static void main(String[] args) {//        List<String> list = new Vector<>();
//        List<String> list = Collections.synchronizedList(new ArrayList<>());List<String> list = new CopyOnWriteArrayList<>();for(int i = 0; i < 30 ; i++){new Thread(() ->{list.add(UUID.randomUUID().toString().substring(0,8));System.out.println(list);},i+"").start();}}
}

运行结果:

2.3.1 CopyOnWrite的缺点

CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。

内存占用问题:因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。

数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。【当执行add或remove操作没完成时,get获取的仍然是旧数组的元素】
  
【完】

:文章内所有测试用例源码:https://gitee.com/wjie2018/test-case.git

Java集合类ArrayList线程不安全验证和解决相关推荐

  1. java集合类的线程安全_Java集合中的线程安全问题

    介绍 JDK中提供了很多集合实现,本文不会介绍有哪些集合的接口以及实现类,而是介绍如何在多线程情况下使用这些集合. - 如果您还不太了解Java的整个集合体系的话,请查看<Java开发成长之路第 ...

  2. Java集合类ArrayList循环中删除特定元素

    在项目开发中,我们可能往往需要动态的删除ArrayList中的一些元素. 一种错误的方式: [java] view plain copy for(int i = 0 , len= list.size( ...

  3. ArrayList线程不安全三种解决情况

    在多线程高并发的情况下arrayList会抛出concurrentModificationException 解决情况 1.vector 2. Collections.synchronizedList ...

  4. java面试题29 牛客 以下关于集合类ArrayList、LinkedList、HashMap描述

    java面试题29 牛客 以下关于集合类ArrayList.LinkedList.HashMap描述错误的是() A HashMap实现Map接口,它允许任何类型的键和值对象,并允许将null用作键或 ...

  5. 集合类ArrayList、HashMap、HashSet线程不安全

    为了提高写入性能JDK 1.2 引入了非线程安全容器ArrayList替代Vector package Juc;import java.util.ArrayList; import java.util ...

  6. Java集合类学习UML图——ArrayList

    Java集合类学习UML图--ArrayList ArrayList类的定义 public class ArrayList<E> extends AbstractList<E> ...

  7. java arraylist线程安全_ArrayList升级为线程安全的List

    我们都熟知在高并发的场景下,ArrayList是线程不安全的,JDK Collections接口提供线程安全的操作,本文通过代码演示下,最后查看源码分析下为何是线程安全的. ArrayList升级为线 ...

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

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

  9. JAVA笔记- JAVA集合类之ArrayList详解

    1- 集合概述 A) 面向对象编程语言对事物的描述都是通过对象来体现的. 为了方便对多个对象进行操作,我们就必须对这多个对象进行存储,而要想对多个对象进行存储,就不能是一个基本的变量,而应该是一个容器 ...

最新文章

  1. 包揽全球50%以上份额,中美发力超级计算
  2. 学习Docker容器时,错误bash: ping: command not found的解决方法
  3. js 移动端 滑块验证码插件_VUE技术详解,Vue.js从入门到精通
  4. TCP/IP协议简介2
  5. SAP ABAP Netweaver Note download debug
  6. dp聚类算法_【深度】基于残差分析的混合属性数据聚类算法
  7. SQL性能第2篇:查询分析和访问路径制定
  8. iOS12.3正式版发布 iOS13亮相进入倒计时
  9. 2018软工第六次作业
  10. 复制加网站信息的javascript代码及对应的javascript阻止命令
  11. amazeui学习笔记--js插件(UI增强4)--下拉组件Dropdown
  12. 慕课网 jupyter notebook魔法方法学习小记
  13. 【控制】《最优控制理论与系统》-胡寿松老师-目录
  14. 使用excel校验身份证号码是否正确
  15. Linux -- fflush函数
  16. app显示服务器图片不显示,如何将存在本地服务器的图片,在APP前台显示
  17. 运营人员必知!SPU和SKU是什么?
  18. AWS KVS(Kinesis Video Streams)之WebRTC移植编译(五)
  19. Bitmap的图片压缩汇总
  20. 【开发工具】C/C++开发者必不可少的15款编译器+IDE

热门文章

  1. 外包公司:这位高颜值女程序媛请留步!
  2. 斗音视频制作技巧和分享技巧
  3. deepstream-测试发送kafka
  4. 40岁想在职读计算机博士,年龄超过四十五岁还有机会报考在职博士吗
  5. mysql Incorrect string value \xF0\x9F\x98\x84\xF0\x9F
  6. ApiCloud组件
  7. 新浪开发者平台(Sina App Engine)初探
  8. QT手动添加Q_OBJECT报错解决方法记录
  9. frp内网穿透https
  10. java Math类的常用方法介绍