JUC之集合中的线程安全问题
集合线程安全问题
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修饰的,具体的添加的流程,代码没读懂,暂且不表。
欢迎朋友分享下该部分的优质博客。
JUC之集合中的线程安全问题的更多相关文章
- 浅谈利用同步机制解决Java中的线程安全问题
我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等 ...
- iOS中的线程安全问题
为了保证线程安全,不会因为多个线程访问造成资源抢夺,出现的运行结果的偏差问题,我们需要使用到线程同步技术,最常用的就是 @synchronized互斥锁(同步锁).NSLock.dispatch_se ...
- java设计模式--解决单例设计模式中懒汉式线程安全问题
首先写个单例,懒汉模式: public class SingleDemo { private static SingleDemo s = null; private SingleDemo(){} pu ...
- 逐步理解Java中的线程安全问题
什么是Java的线程安全问题? 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读/写完,其他线程才可使用.不会出现数据不一致或者数据 ...
- Linux/Unix编程中的线程安全问题【转】
转自:http://blog.csdn.net/zhengzhoudaxue2/article/details/6432984 在目前的计算机科学中,线程是操作系统调度的最小单元,进程是资源分配的最小 ...
- Parallel线程安全问题
废话不多说,上代码: using System; using System.Collections.Generic; using System.Threading.Tasks; namespace P ...
- stl空间配置器线程安全问题补充
摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关 ...
- (2.1)servlet线程安全问题
本文参考链接:http://www.yesky.com/334/1951334.shtml 摘 要:介绍了Servlet多线程机制,通过一个实例并结合Java 的内存模型说明引起Servlet线程不安 ...
- servlet开发(二)之servlet的线程安全问题
之所以考虑线程安全问题,是因为引入了多线程.多线程指的是这个程序(一个进程)运行时产生了不止一个线程.如果不考虑多线程的话,程序执行只有一条路径,就像人在敲代码的时候只能敲代码,不能戴上耳机听歌.引入 ...
随机推荐
- 大数据学习day20-----spark03-----RDD编程实战案例(1 计算订单分类成交金额,2 将订单信息关联分类信息,并将这些数据存入Hbase中,3 使用Spark读取日志文件,根据Ip地址,查询地址对应的位置信息
1 RDD编程实战案例一 数据样例 字段说明: 其中cid中1代表手机,2代表家具,3代表服装 1.1 计算订单分类成交金额 需求:在给定的订单数据,根据订单的分类ID进行聚合,然后管理订单分类名称, ...
- 零基础学习java------day18------properties集合,多线程(线程和进程,多线程的实现,线程中的方法,线程的声明周期,线程安全问题,wait/notify.notifyAll,死锁,线程池),
1.Properties集合 1.1 概述: Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串 一个属性列表可包含另 ...
- 【Reverse】每日必逆0x01
附件:https://files.buuoj.cn/files/7458c5c0ce999ac491df13cf7a7ed9f1/SimpleRev 题解 查壳 64位ELF文件,无壳 IDApro处 ...
- C++中union相关
前两天做阿里笔试遇到一个选择题题目大概是 #include <iostream> #include <stdlib.h> using namespace std; union ...
- Android 极光推送集成
集成Jpush 1.用Android Studio创建一个Demo 2.创建激光推送开发者账号,要创建极光推送开发者帐号,请访问极光推送官方网站https://www.jiguang.cn/push ...
- PHP之CURL实现含有验证码的模拟登录
博主最近在为学校社团写一个模拟登录教务系统来进行成绩查询的功能,语言当然是使用PHP啦,原理是通过php数据传输神器---curl扩展,向学校教务系统发送请求,通过模拟登录,获取指定url下的内容. ...
- 网络协议之:基于UDP的高速数据传输协议UDT
目录 简介 UDT协议 UDT的缺点 总结 简介 简单就是美.在网络协议的世界中,TCP和UDP是建立在IP协议基础上的两个非常通用的协议.我们现在经常使用的HTTP协议就是建立在TCP协议的基础上的 ...
- nodejs+koa+uniapp实现微信小程序登陆获取用户手机号及openId
nodejs+koa+uniapp实现微信小程序登陆获取用户手机号及openId 前言: 我准备用nodejs+koa+uniapp实现一款餐饮点单小程序,以及nodejs+koa+vue实现后端管理 ...
- MySQL 分区表,为什么分区键必须是主键的一部分?
随着业务的不断发展,数据库中的数据会越来越多,相应地,单表的数据量也会越到越大,大到一个临界值,单表的查询性能就会下降. 这个临界值,并不能一概而论,它与硬件能力.具体业务有关. 虽然在很多 MySQ ...
- Python的动态语言特性; __slots__属性
python是动态语言 1. 动态语言的定义 动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用.它是一类 在运行时可以改变其结构的语言 :例如新的函数.对象.甚至代码可以被 ...