线程安全与不安全集合

线程不安全集合:

  • 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集合多线程安全的更多相关文章

  1. 备战金三银四!一线互联网公司java岗面试题整理:Java基础+多线程+集合+JVM合集!

    前言 回首来看2020年,真的是印象中过的最快的一年了,真的是时间过的飞快,还没反应过来年就夸完了,相信大家也已经开始上班了!俗话说新年新气象,马上就要到了一年之中最重要的金三银四,之前一直有粉丝要求 ...

  2. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  3. Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

    概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...

  4. Java Thread 多线程 介绍

    1.线程概述 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程. 当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 2.线程 ...

  5. Java集合面试题

    1.Java集合框架是什么?说出一些集合框架的优点? 每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector.Stack.HashTable和Array.随着集合的广泛使用,Java1 ...

  6. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  7. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  8. Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例

    概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Ha ...

  9. Java集合系列:-----------04fail-fast总结(通过ArrayList来说明fail-fast的原理以及解决办法)

    前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例3 f ...

随机推荐

  1. vue 中 使用 element-ui 发送请求前 校验全部表单,报警告: [Element Warn][Form]model is required for validate to work!

    WEB先生 2020-07-14 20:01:45  754  收藏 分类专栏: vue 文章标签: vue js 版权 报这种错可能有以下两种情况 1.属性绑定错误,确保绑定的是  :model  ...

  2. MLlib学习——基本数据类型

    数据类型--基于RDD的API 本地矢量 标记点 本地矩阵 分布式矩阵 RowMatrix(行矩阵) IndexedRowMatrix(索引行矩阵) CoordinateMatrix(坐标矩阵) Bl ...

  3. 年前最后一次2022.1.28_RP++

    T1同昨(我看到题目就粘上昨天的代码,结果题还没发我就A了hhhhhh) T2一开始想用深搜,结果T掉了...只好改广搜,就挺令人头大 点击查看宽广对比 #include<bits/stdc++ ...

  4. C++ 实现 Parsec

    前一段时间看到了梨梨喵聚聚写的Parser Combinator 在 C++ 里的 DSL, 感觉好厉害, 正好毕设里要写一部分前端, 昨天又把这篇文章看了一遍, 想着我也要用这么酷炫的东西来参与一下 ...

  5. [LeetCode]28.实现strStr()(Java)

    原题地址: implement-strstr 题目描述: 实现 strStr() 函数. 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字 ...

  6. 【lwip】lwip源码基础

    目录 前言 概念&作用 网络接口 概念引入 总结 lwip netif 结构体 链接 字段分析 网卡链表 网络 IP 接收数据函数 发送数据函数 ARP 模块调用的发送函数 出口回调函数 用户 ...

  7. excel写入数值型内容

    一开始的想法是设置单元格格式为数值型 XSSFDataFormat format= (XSSFDataFormat) workbook.createDataFormat(); CellStyle cs ...

  8. 深入理解Java类加载机制,再也不用死记硬背了

    谈谈"会"的三个层次 在<说透分布式事务>中,我举例里说明了会与会的差别.对一门语言的学习,这里谈谈我理解的"会"的三个层次: 第一层:了解这门语言 ...

  9. 思迈特软件Smartbi:专注BI,把产品打造到极致

    在企业服务领域,现在的BI(商业智能)无疑是妥妥的风口.不过在20多年前,BI却完全是一幅门庭冷清宾客稀的光景--小型企业高攀不起,大型企业爱答不理. 一些管理者们理所当然地认为,商业是人脑的高阶竞争 ...

  10. 【C# .Net GC】条件自动垃圾回收 HandleCollector类

    条件自动回收 达到指定条件后自动执行GC回收垃圾. GC中的方法AddMemoryPressure和RemoveMemoryPressure 本机资源有时会消耗大量内存,但用于包装它的托管对象只占用很 ...