集合类线程安全吗?ConcurrentModification异常遇到过吗?如何解决?
集合类不安全的问题
1. ArrayList的线程不安全问题
1.1 首先回顾ArrayList底层
- ArrayList的底层数据结构是数组
- 底层是一个
Object[] elementData
的数组,初始化默认为空数组 - 默认容量
DEFAULT_CAPACITY
为10,如果容量不够调用grow()
方法,将容量调整为原来的1.5倍,核心代码为int newCapacity = oldCapacity + (oldCapacity >> 1);
- 扩容过程是首先创建出来一个新数组,之后使用
Arrays.copyOf(elementData, newCapacity)
将原数组内容拷贝到新数组
回到本节知识,
1.2 为什么说ArrayList会存在线程不安全问题?
很简单的一个示例代码:
package com.yuxue.juc.collection;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},"Thread"+ i).start();
}
}
}
此时程序会报异常:
出现了一个java.util.ConcurrentModificationException
异常!
为什么会出现这样的结果?首先我们都知道,在调用ArrayList时,底层add方法是没有加synchronized即没有加锁的,当不同的线程调用方法时,会出现不安全的问题
1.3 为什么会出现这种问题?
并发修改这个list所导致的
一个线程正在写入,另一个线程来抢夺,导致数据不一致,并发修改异常
1.4 解决方案?
1.4.1 Vector
- Vector底层加了锁,加锁数据一致性一定可以保证,但是并发性急剧下降!
- ArrayList就是牺牲线程安全性才提出的
- 但是Vector是在JDK1.0已经出现的,ArrayList在JDK1.2版本出现的
- 所以用Vector可以但是效率太低,那么有没有其他的工具类可以满足?
1.4.2 synchronizedList
将代码改为以下的代码即可
List<String> list = Collections.synchronizedList(new ArrayList<>());
1.4.3 CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();//写时复制,读写分离
CopyOnWriteArrayList.add
方法:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取原来的数组,保存副本为elements
Object[] elements = getArray();
//获取原数组长度
int len = elements.length;
//将其拷贝到新数组newElements,长度为原数组加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//第len上元素为新添加值e
newElements[len] = e;
//设置新数组为newElements
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
CopyOnWrite容器即写时复制(一种读写分离的思想),往一个元素添加容器的时候,不直接往当前容器Object[]
添加,而是先将当前容器 Object[]
进行copy,复制出一个新的容器Object[] newElements
,让后新的容器添加元素,添加完元素之后,再将原 容器的引用指向新的容器setArray(newElements),
这样做可以对CopyOnWrite容器进行并发的读,而不需要加锁, 因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
2. Set的线程不安全问题
HashSet线程不安全,同样报错为java.util.ConcurrentModificationException
异常!
解决的类或者方法:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
其底层还是CopyOnWriteArrayList
,因为其源码为:
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
HashSet的底层是HashMap!
/**
* Constructs a new, empty set; the backing HashMap instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
初始容量为16,默认负载因子为0.75的标准的HashMap!
其中底层还有一个很重要的问题,当HashSet调用add(e)
方法是,如果是HashMap,其Key为e,value值为什么?此时通过源码我们可以得到:
public boolean add(E e) {
//key为e,value为PRESENT
return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
3. Map的线程不安全问题
HashMap是线程不安全的,想解决可以用
- Hashtable
- ConcurrentHashMap
- Collections.synchronizedMap
3.1 HashMap和Hashtable区别?
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度
- HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)
- HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好
- 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别
- 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
- HashMap不能保证随着时间的推移Map中的元素次序是不变的
3.2HashMap与ConcurrentHashMap区别?
准备回头写一篇博客专门总结,先留着 : )
集合类线程安全吗?ConcurrentModification异常遇到过吗?如何解决?的更多相关文章
- c#线程池中的异常
static void Main(string[] args) { //写日志 //使用线程池 ; i < ; i++) { ThreadPool.QueueUserWorkItem(new W ...
- 一个解决在非UI线程中访问UI 异常的小方法
写 WPF 的童鞋可能都会碰到 在非UI线程中访问 UI 异常的问题.这是为了防止数据不一致做的安全限制. 子线程中更新UI还要交给主线程更新,引用满天飞,实在是麻烦. 接下来,我们推出一个可以称之为 ...
- Java中主线程如何捕获子线程抛出的异常
首先明确线程代码的边界.其实很简单,Runnable接口的run方法所界定的边界就可以看作是线程代码的边界.Runnable接口中run方法原型如下: public void run(); 而所有的具 ...
- ajax--->请求异常 jQuery提示parsererror错误解决办法
ajax请求异常 jQuery提示parsererror错误解决办法 原因:出现这个错误是因为后端返回的数据类型和前端请求中dataType的要求类型不一致导致的. dataType简介:jquery ...
- java线程基础巩固---如何捕获线程运行期间的异常
对于友盟统计我想搞程序的应该无人不晓,其中对于里面用得最多的功能就是对线上的崩溃进行修复,而这些异常都是运行期的,如: 其实也就是可以对线程中出现了这种运行期异常是提供有一种捕获机制对其进行统一处理, ...
- 工具类ToastUtil 避免在子线程中使用抛异常 "Can't create handler inside thread that has not called Looper.prepare()"
package com.example.kbr.utils; import android.view.Gravity; import android.widget.Toast; import io.r ...
- netload 加载程序集抛异常----无法加载程序集解决办法
netload 加载程序集抛异常----无法加载程序集 错误信息如下: 无法加载程序集.错误详细信息: System.BadImageFormatException: 未能加载文件或程序集“file: ...
- 线程中无法实例化spring注入的服务的解决办法
问题描述 在Java Web应用中采用多线程处理数据,发现Spring注入的服务一直报NullPointerException.使用注解式的声明@Resource和XML配置的bean声明,都报空指针 ...
- android 自定义adapter和线程结合 + ListView中按钮滑动后状态丢失解决办法
adapter+线程 1.很多时候自定义adapter的数据都是来源于服务器的,所以在获取服务器的时候就需要异步获取,这里就需要开线程了(线程池)去获取服务器的数据了.但这样有的时候adapter的中 ...
随机推荐
- [刷题] 455 Assign Cookies
要求 贪心算法的关键:判断问题是否可以用贪心算法解决 给小朋友们分饼干,每个小朋友"贪心指数"为g(i),饼干大小值s(i) g(i):小朋友需要的饼干大小的最小值 若s(j)&g ...
- [Java] 部署到Linux
阿里云 控制台->云服务器ECS->实例->创建实例 计费方式 地域 网络 安全组:默认安全组 公网IP地址:分配 实例 公网带宽:1M ECS服务器:公共镜像CentOS 存储 购 ...
- 安装SpecCPU2006 on Linux of CentOS6.3, gcc4.4.7
安装SpecCPU2006 on Linux of CentOS6.3, gcc4.4.7 由于在tools/bin目录中只有ia64-linux,所以在直接运行./install.sh脚本时,系统会 ...
- 041.Python守护进程,锁信号量和事件
一 守护进程 1.1 基本概念 守护进程 正常情况下,主进程默认等待子进程调用结束之后结束 守护进程在主进程执行代码结束后,自动终止 守护进程语法: 进程对象.daemon = True ,设置该进程 ...
- Java和JDK版本的关系-(转载)
JAVA的版本最开始是1995年的JDK Alpha and Beta版本,第二年发布JDK1.0版本之后就是JDK1.1,JDK1.2.到1998年,不再叫JDK了,而是叫J2SE,但是版本号还是继 ...
- 矩阵中的路径 DFS+剪枝
给定一个 m x n 二维字符网格 board 和一个字符串单词 word .如果 word 存在于网格中,返回 true :否则,返回 false . 单词必须按照字母顺序,通过相邻的单元格内的字母 ...
- Linux(CentOS7)下配置多个tomcat
记录 Linux(CentOS7) 下配置多个 tomcat 的操作过程. 一.下载tomcat 前提:安装配置好jdk环境,未配置可参考Linux(CentOS7)下安装jdk1.8. 从 tomc ...
- Python小白的数学建模课-04.整数规划
整数规划与线性规划的差别只是变量的整数约束. 问题区别一点点,难度相差千万里. 选择简单通用的编程方案,让求解器去处理吧. 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛达 ...
- windows下nginx配合nodejs进行反向代理
本文原创,转载请附上原作者链接!https://www.cnblogs.com/LSWu/articles/14848324.html 1.安装node.js 从node.js官网上下载node.js ...
- 行者APP适配国外环境问题解决
(本文1151字,阅读约5分钟) 玩骑行的同伴都知道,长途骑行,第一需要好的硬件,如大腿发动机.车子.装备等:二是需要好的软件,如意志.有氧能力.骑行app等. 到雅加达后,才发现在国内用了几年的黑鸟 ...