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 ...
随机推荐
- curl 查看接口的网络分段响应时间
示例如下 curl -o /dev/null -s -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_tota ...
- new方法实现原理
new方法实现原理 完整的创建一个可用的对象:Person *p=[Person new]; new方法的内部会分别调用两个方法来完成3件事情: (1)使用alloc方法来分配存储空间(返回分配的对象 ...
- 分配IP地址的好东西 DHCP以及NAT简单介绍
主机配置协议DHCP 1.DHCP应用场景 2.DHCP基础原理 3.NAT简单介绍 4.配置命令 1.手工配置IP地址,工作量比较大而且不好管理,如果用户自己修改参数,可能会导致ip地址冲突,这个时 ...
- python 异常捕捉总结
Process finished with exit code -1 错误 执行代码 pycharm2020.1中手动中断程序,可是却捕捉不了中断异常,并且输出Process finished wit ...
- 分享刚出炉的基于Blazor技术的Web应用开发框架
这是最近刚刚重构完成的项目,有点迫不及待的分享给大家,为了跟上技术升级把原来基于MVC Razor Page开源项目 RazorPageCleanArchitecture 进行重构, 前端用Blazo ...
- Solution -「NOI 2016」「洛谷 P1587」循环之美
\(\mathcal{Description}\) Link. 给定 \(n,m,k\),求 \(x\in [1,n]\cap\mathbb N,y\in [1,m]\cap \mathbb ...
- CreateEvent进程同步
CreateEvent进程间同步 CreateEvent可以创建或是打开一个命名或是未命名的event对象. HANDLE CreateEvent( LPSECURITY_ATTRIBUTES ...
- set和setenv
今天用set设置PATH变量(加一个路径),发现虽然echo的时候显示修改成功了,实际执行命令的时候确没有去那个路径查找:当前shell是c shell(csh). 在网上找了一些材料,总结如下: ...
- 分析CVE-2018-18557与复现
前言 cve描述: LibTIFF 4.0.9 (with JBIG enabled) decodes arbitrarily-sized JBIG into a buffer, ignoring t ...
- 轻量级DI框架Guice使用详解
背景 在日常写一些小工具或者小项目的时候,有依赖管理和依赖注入的需求,但是Spring(Boot)体系作为DI框架过于重量级,于是需要调研一款微型的DI框架.Guice是Google出品的一款轻量级的 ...