集合类不安全的问题

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是线程不安全的,想解决可以用

  1. Hashtable
  2. ConcurrentHashMap
  3. Collections.synchronizedMap

3.1 HashMap和Hashtable区别?

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的

3.2HashMap与ConcurrentHashMap区别?

准备回头写一篇博客专门总结,先留着 : )

集合类线程安全吗?ConcurrentModification异常遇到过吗?如何解决?的更多相关文章

  1. c#线程池中的异常

    static void Main(string[] args) { //写日志 //使用线程池 ; i < ; i++) { ThreadPool.QueueUserWorkItem(new W ...

  2. 一个解决在非UI线程中访问UI 异常的小方法

    写 WPF 的童鞋可能都会碰到 在非UI线程中访问 UI 异常的问题.这是为了防止数据不一致做的安全限制. 子线程中更新UI还要交给主线程更新,引用满天飞,实在是麻烦. 接下来,我们推出一个可以称之为 ...

  3. Java中主线程如何捕获子线程抛出的异常

    首先明确线程代码的边界.其实很简单,Runnable接口的run方法所界定的边界就可以看作是线程代码的边界.Runnable接口中run方法原型如下: public void run(); 而所有的具 ...

  4. ajax--->请求异常 jQuery提示parsererror错误解决办法

    ajax请求异常 jQuery提示parsererror错误解决办法 原因:出现这个错误是因为后端返回的数据类型和前端请求中dataType的要求类型不一致导致的. dataType简介:jquery ...

  5. java线程基础巩固---如何捕获线程运行期间的异常

    对于友盟统计我想搞程序的应该无人不晓,其中对于里面用得最多的功能就是对线上的崩溃进行修复,而这些异常都是运行期的,如: 其实也就是可以对线程中出现了这种运行期异常是提供有一种捕获机制对其进行统一处理, ...

  6. 工具类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 ...

  7. netload 加载程序集抛异常----无法加载程序集解决办法

    netload 加载程序集抛异常----无法加载程序集 错误信息如下: 无法加载程序集.错误详细信息: System.BadImageFormatException: 未能加载文件或程序集“file: ...

  8. 线程中无法实例化spring注入的服务的解决办法

    问题描述 在Java Web应用中采用多线程处理数据,发现Spring注入的服务一直报NullPointerException.使用注解式的声明@Resource和XML配置的bean声明,都报空指针 ...

  9. android 自定义adapter和线程结合 + ListView中按钮滑动后状态丢失解决办法

    adapter+线程 1.很多时候自定义adapter的数据都是来源于服务器的,所以在获取服务器的时候就需要异步获取,这里就需要开线程了(线程池)去获取服务器的数据了.但这样有的时候adapter的中 ...

随机推荐

  1. 联想RD350板载RAID110i,安装CentOS 7 不识别RAID设备

    联想RD350板载RAID110i,安装CentOS 7 不识别RAID设备   情况如题所述. 1. 确认BIOS中 Boot mode为[UEFI]或者[AUTO] 2. 确认BIOS中 Stor ...

  2. Msf--永恒之蓝 ms17_010

    |>>>中华人民共和国网络安全法<<<|警告:请勿用于非法用途,后果自负! 简介 一.概述 永恒之蓝是指2017年4月14日晚,黑客团体Shadow Brokers ...

  3. setting>SSH>sessions setting>勾选ssh Keepalive[ MobaXterm】设置保持SSH连接

    [ MobaXterm]设置保持SSH连接 ssh远程连接会在无操作时自动断开连接.为了保持程序运行和连接,需要设置保持连接. 1.MobaXterm如果使用了MobaXterm客户端,那么需要在设置 ...

  4. shell 读取某个目录下的所有文件

    #!/bin/shFILE_PATH="xxx" xxx:路径cd $FILE_PATHfor FILE in `ls` do echo $FILE done

  5. strcasecmp函数和strncasecmp函数原型

    函数说明 strcasecmp()用来比较参数s1和s2字符串,比较时会自动忽略大小写的差异. 返回值    若参数s1和s2字符串相同则返回0.s1长度大于s2长度则返回大于0 的值,s1 长度若小 ...

  6. ioctl 函数的FIOREAD参数

    在学习ioctl 时常常跟 read, write 混淆.其实 ioctl 是用来设置硬件控制寄存器,或者读取硬件状态寄存器的数值之类的. 而read,write 是把数据丢入缓冲区,硬件的驱动从缓冲 ...

  7. java 集合梳理

    使用 processOn 画的java 集合图谱,应付面试应该可以了

  8. haproxy env 安装与基础配置

    1. 安装 Use docker.package or source installations to install 第三方仓库 https://pkgs.org/download/haproxy ...

  9. 动态更换animator的animatorcontroller

    你可以这样 Animator animator = this.gameObject.GetComponent<Animator>(); animator.runtimeAnimatorCo ...

  10. HTML中option的单页调用

    我们在用到下拉列表框select时,需要对选中的<option>选项触发事件,其实<option>本身没有触发事件方法,我们只有在select里的 onchange方法里触发. ...