Java集合多线程安全
线程安全与不安全集合
线程不安全集合:
- ArrayList
- LinkedList
- HashMap
- HashSet
- TreeMap
- TreeSet
- StringBulider
线程安全集合:
- Vector
- HashTable
- Properties
集合线程安全与解决方案
ArrayList线程安全问题
package com.raicho.mianshi.mycollection; import java.util.ArrayList;
import java.util.List;
import java.util.UUID; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 15:32
**/
public class ArrayListConcurrentDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; ++i) {
new Thread(() -> {
list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
System.out.println(list);
}).start();
}
}
}
运行报错:

ArrayList是线程不安全的,add()方法并没有加锁(synchronized),多线程环境下会抛出ConcurrentModificationException
解决方案:
- 使用Vector类(使用了synchronized),效率极低

- 使用Collections.synchronizedList(new ArrayList<>()):内部直接将接受的List对象传递给静态内部类SynchronizedList对象,然后Collections.synchronizedList(new ArrayList<>())返回的List对象的调用方法都是直接调用输入List对象的方法,但是加了synchronized,类似装饰器模式,也是对输入List的一种增强:
package com.raicho.mianshi.mycollection; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 15:32
**/
public class ArrayListConcurrentDemo { public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 50; ++i) {
new Thread(() -> {
list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
源码:
static <T> List<T> synchronizedList(List<T> list, Object mutex) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list, mutex) :
new SynchronizedList<>(list, mutex));
}
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
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);}
}
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
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator<? super E> c) {
synchronized (mutex) {list.sort(c);}
}
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
- CopyOnWriteArrayList:写时复制是一种读写分离的思想,在并发读的时候不需要加锁,因为它能够保证并发读的情况下不会添加任何元素。而在并发写的情况下,需要先加锁,但是并不直接对当前容器进行写操作。而是先将当前容器进行复制获取一个新的容器,进行完并发写操作之后,当之前指向原容器的引用更改指向当前新容器。也就是说,并发读和并发写是针对不同集合,因此不会产生并发异常
package com.raicho.mianshi.mycollection; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 15:32
**/
public class ArrayListConcurrentDemo { public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; ++i) {
new Thread(() -> {
list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
源码:
// CopyOnWriteArrayList.java
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 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 semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
} // 读操作不加锁
private E get(Object[] a, int index) {
return (E) a[index];
}
在添加元素e完后,再调用setArray(newElements);函数重新赋值,之前指向原容器的引用更改指向当前新容器
HashSet线程安全问题
HashSet底层就是一个HashMap,默认的HashSet是一个初始大小为16,负载因子为0.75的HashMap:

HashSet的多线程安全问题实际上就是HashMap的多线程安全问题:
package com.raicho.mianshi.mycollection; import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 17:03
*
* HashSet多线程不安全问题
* HashSet底层就是HashMap,因此这个案例也是HashMap多线程不安全问题的演示
*/
public class HashSetThreadUnsafe {
public static void main(String[] args) {
Set<String> sets = new HashSet<>();
for (int i = 0; i < 100; ++i) {
new Thread(() -> {
sets.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(sets);
},String.valueOf(i)).start();
}
}
}

解决方案:
- Collections集合类的static方法SynchronizedSet
- CopyOnWriteArraySet:也是写时复制思想,但是内部还是使用CopyOnWriteArrayList实现:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList<E> al; /**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
// 构造器内部实例化了一个CopyOnWriteArrayList
al = new CopyOnWriteArrayList<E>();
}
// ...
}
HashMap多线程安全的解决方案
相比于HashSet,HashMap除了可以使用Collections集合类的synchronizedMap方法外,还可以使用juc包下ConcurrentHashMap类。
package com.raicho.mianshi.mycollection; import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 17:03
*/
public class HashMapThreadUnsafe {
public static void main(String[] args) {
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 100; ++i) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 4));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Java集合多线程安全的更多相关文章
- 备战金三银四!一线互联网公司java岗面试题整理:Java基础+多线程+集合+JVM合集!
前言 回首来看2020年,真的是印象中过的最快的一年了,真的是时间过的飞快,还没反应过来年就夸完了,相信大家也已经开始上班了!俗话说新年新气象,马上就要到了一年之中最重要的金三银四,之前一直有粉丝要求 ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)
概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...
- Java Thread 多线程 介绍
1.线程概述 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程. 当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 2.线程 ...
- Java集合面试题
1.Java集合框架是什么?说出一些集合框架的优点? 每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector.Stack.HashTable和Array.随着集合的广泛使用,Java1 ...
- Java集合源码分析(四)Vector<E>
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例
概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Ha ...
- Java集合系列:-----------04fail-fast总结(通过ArrayList来说明fail-fast的原理以及解决办法)
前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例3 f ...
随机推荐
- Mac 使用自带php和Apache 安装配置Xdebug 开启本地调试模式
Mac 安装配置php xdebug 本地调试 0.原理图 https://paper.seebug.org/308/ 测试demo构建方法 新建空白项目,目录选择Apache默认项目目录 1.下载x ...
- lua语言:string
转载请注明来源:https://www.cnblogs.com/hookjc/ 字符串库函数string.len(s) 返回字符串s的长度:string.rep(s, n) ...
- Netty入门使用教程
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11827026.html 本文介绍Netty的使用, 结合我本人的一些理解和操作来快速的让初学者 ...
- UITextView模拟UITextField 设置Placeholder属性 --董鑫
由于最近有用到输入框,刚开始考虑的是UITextField,因为它在没有输入的时候可以有提示的Placeholder更能,很人性化,但UITextField只能单行输入,不能跳行,对于一些强迫症的亲来 ...
- MLlib学习——基本数据类型
数据类型--基于RDD的API 本地矢量 标记点 本地矩阵 分布式矩阵 RowMatrix(行矩阵) IndexedRowMatrix(索引行矩阵) CoordinateMatrix(坐标矩阵) Bl ...
- Python实现不带头结点的单链表
1 # 创建一个节点类 2 class Node: 3 def __init__(self, item): 4 self.item = item 5 self.next = None 6 7 8 # ...
- Linux-CPU优化之平均负载率
一.平均负载率定义 平均负载是指单位时间内,系统处于可运行状态 和不可中断状态 的平均进程数,也就是平均活跃进程数,它和CPU 使用率并没有直接关系. 可运行状态的进程:是指正在使用 CPU 或者正在 ...
- Nginx-反向代理服务器
概述 Nginx是一款轻量级的Web服务器.反向代理服务器,由于它的内存占用少,启动极快,高并发能力强,在互联网项目中广泛应用. 优点 nginx是多进程的,不会出现并发问题,不用加锁.一个进程出问题 ...
- 内省机制(操作javaBean的信息)
内省机制(操作javaBean的信息) ----是不是联想到了反射机制了哈,这两者有什么区别呢? 1.内省机制和反射机制的联系 ■ 其实内省机制也是通过反射来实现的,而反射是对一切类都适合去动态获取类 ...
- OpenLDAP测试搭建
目录 ldap介绍 测试环境 安装LDAP服务端 设置LDAP的root密码 配置LDAP服务端 创建LDAP证书 设置LDAP数据库 创建LDAP用户 添加防火墙规则 开启LDAP日志 配置LDAP ...