集合线程安全问题

JDK Version:9

首先说下集合线程安全是什么:当多个线程对同一个集合进行添加和查询的时候,出现异常错误。

复现例子:

package com.JUC;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID; public class ListSecutity04 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list);
},String.valueOf(i)).start();
}
}
}

效果图:

可以看到报ConcurrentModificationException并发修改异常;出现该错误的问题是,在ArrayList中的add方法没有加锁。

查看其源码:

public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
//----------------------------------------
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
//----------------------------------------
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}

解决方式-:Vector和Conllections

见名知意,标题的两种方法都是比较古老的方法,使用Vector是因为其add方法中加的sychronized关键字修饰的

public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}

另一种方法是在Collection中的工具类synchronizedCollection(Collection<T> c)返回指定 collection 支持的同步(线程安全的)collection。

反复执行测试,代码通过:

package com.JUC;

import java.util.*;

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

虽然但是,我们选择CopyOnWriteArrayList;

解决方式二:CopyOnWriteArrayList

写时复制技术:

List<String> list = new CopyOnWriteArrayList<>();

其思想:

在多线程的情况下,当对集合进行写的操作的时候,系统先将原来的内容(A)复制一份为(B),原来的内容(A)可以进行并发读,复制后的内容(B)写入新的内容,当内容写入完成后,A与B实现覆盖或者说合并。

其中数组的定义我们使用的是volatile定义的,这样,每个线程可以实时的观察到数组的变化。

private transient volatile Object[] array;
final void setArray(Object[] a) {
array = a;
}

add()方法源码:对lock对象加了锁

final transient Object lock = new Object();
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray(); //获取原内容
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); //数组复制
newElements[len] = e; //添加新的内容
setArray(newElements); //覆盖原数组
return true;
}
}

CopyOnWriteArrayList最大特点,读写分离,最终一致。比synchronized悲观锁性能较好。缺点就是,复制需要占用内存,可能出现OOM的情况。

与之类似线程不安全的集合有:HashMap,HashSet,其解决方法类似,在JUC中都有对应,

我们也可以进行代码的编写,并进入源码简单分析一下:

HashSet--CopyOnWriteArraySet

首先查看HashSet中的add方法的源码:(线程不安全)

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//---------map中
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

从中可以看到,set集合底层使用的是map集合中的put方法,进入其方法中查看,是没有同步相关的代码修饰的。

也可以看出来Set集合是无序且不重复的,set中传入的E最后被传入到map中作为key。

然后查看下CopyOnWriteArraySet中add的源码:(线程安全)

public boolean add(E e) {
return al.addIfAbsent(e); //CopyOnWriteArrayList<E> al = new CopyOnWriteArrayList<E>()
}
//-------------addIfAbsent---------------
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
//---------------------addIfAbsent------------------------
private boolean addIfAbsent(E e, Object[] snapshot) {
synchronized (lock) {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i]
&& Objects.equals(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}

从源码可以看到,set使用的是CopyOnWriteArrayList,最终添加的数据在addIfAbsent方法中进行了同步。

HashMap--ConcurrentHashMap

HashMap解决的方式就是ConcurrentHashMap,其源码中采用的也是synchronized修饰的,具体的添加的流程,代码没读懂,暂且不表。

欢迎朋友分享下该部分的优质博客。

最新文章

  1. QuartusII Design partion and logic lock
  2. spring mvc 4.3.2 + mybatis 3.4.1 + mysql 5.7.14 +shiro 幼儿园收费系统 之 动态组合条件查询
  3. Atitit 项目培训与学校的一些思路总结
  4. jquery_layout
  5. W3School-CSS 边框(border)实例
  6. 状态压缩 poj 3254
  7. qt中文乱码问题
  8. Linq动态查询简易解决之道(原创)
  9. 2011-2015年Journal of Mathematical Physics高引用文章
  10. ELK+redis集群搭建
  11. java遍历Map的几种方式
  12. APK的目录结构
  13. js 日期修改
  14. delphi 中COPY()函数的意思
  15. nyoj_83:迷宫寻宝(二)(计算几何)
  16. JavaSE(十)之Map总结
  17. Android简易实战教程--第二十四话《画画板》
  18. 阿里云弹性容器实例产品 ECI ——云原生时代的基础设施
  19. android 网络图片双缓存
  20. DataFrame.nunique(),DataFrame.count()

热门文章

  1. 实现android自动化测试部署与运行Shell脚本分享
  2. 【Linux】【Web】【HTTP】HTTP,TCP,SSL通讯过程
  3. 阿里云esc 安装 docker
  4. list.jsp页面
  5. java中二维数组初始化的几种方法
  6. python初探——pandas使用
  7. VSCode上发布第一篇博客
  8. 学习笔记--html篇(2)
  9. 攻防世界 pwn welpwn
  10. 【web】BUUCTF-web刷题记录