一、List
1、代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
2、故障现象
java.util.ConcurrentModificationException
3、导致原因
一个线程正在写,另一线程过来抢夺,导致数据不一致,即并发修改导致的异常
4、解决方案
- new Vector<>()
- Collections.synchronizedList()
- new CopyOnWriteArrayList<>()
在读多写少的时候推荐使用 CopeOnWriteArrayList 这个类
写时复制,读写分离的思想 好处:读操作完全无锁
使用场景 :写操作非常少的场合,能容忍读写的短暂不一致。
CopyOnWriteArrayList迭代器是只读的,不支持增删改。
5、 CopyOnWriteArrayList源码解读:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
二、Set
1、代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
2、解决方案:
- Collections.synchronizedSet()
- new CopyOnWriteArraySet<>()
3、CopyOnWriteArraySet底层源码:
底层使用CopyOnWriteArrayList
1 2 3 |
|
4、HashSet底层源码
HashSet的key是你add()的值,value是一个叫PRESENT Object类型的常量,即HashSet只关心key
1 2 3 4 5 6 7 8 9 10 |
|
三、Map
1、代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
2、解决方案
- Collections.synchronizedMap()
- new ConcurrentHashMap<>();
参考:https://www.cnblogs.com/wjh123/p/11259409.html
我们知道Set是线程不安全的,请编码写一个不安全的案例并给出解决方案?
1、Set线程不安全问题产生
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ContainerNotSafeDemoTwo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, "T1").start();
}
}
}
程序执行结果如下:报java.util.ConcurrentModificationException异常
2、ConcurrentModificationException产生的原因
一个线程正在写,另一个线程过来抢占资源,会造成数据不一致,进而报并发修改异常。
3、Set线程不安全解决方案
(1)第一种解决方案
使用Collections工具类创建同步集合类
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ContainerSafeSetDemoOne {
public static void main(String[] args) {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, String.valueOf(i)).start();
}
}
}
(2)第二种解决方案
使用并发编程类CopyOnWriteArraySet替换HashSet
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class ContainerSafeSetDemoTwo {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, String.valueOf(i)).start();
}
}
}
CopyOnwriteArraySet容器即写时复制容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object []进行copy,复制出一个新的容器object[] newElements,然后往新的容器Object [] newElements里添加元素,添加元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写是不同的容器。
public boolean add(E e) {
return al.addIfAbsent(e);
}
/**
* Appends the element, if not present.
*
* @param e element to be added to this list, if absent
* @return {@code true} if the element was added
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* A version of addIfAbsent using the strong hint that given
* recent snapshot does not contain e.
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
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] && eq(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;
} finally {
lock.unlock();
}
}
参考:https://blog.csdn.net/longgeqiaojie304/article/details/89819422
集合类不安全操作:只要是在Collections挂过号的都是线程不安全的:
java.util.ConcurrentModificationException:并发修改异常
四、我们知道ArraysList是线程不安全的,请编写一个不安全的案例并给出解决方案:
1. ArraysList:
问题:
1. 当new一个ArrayList的时候底层是啥?
数组;
2. 什么类型的数组?
泛型定义的类型,如果没定义就是一个空的,默认长度为10的Object数组;
3. 扩容:
需要长度大于原数组长度,大小扩大到原值1.5倍,将老数组copy到新数组
4. ArrayList是线程安全的还是线程不安全的?
线程不安全
5. 给我举个线程不安全的例子
java.util.ConcurrentModificationException:并发修改异常
package com.example.code.conllection; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * 1. 故障现象:java.util.ConcurrentModificationException * * 2. 导致原因: * 并发争抢导致,参考花名册签到,一个人正在写入,另一个同学过来抢夺,导致数据不一致,并发修改异常; * 3. 解决方案: * - 用Voctor:数据一致性绝对可以保证,但数据安全性几句下降 * - Collections.synchronizedList(new ArrayList<>()):Collections工具类 * - java.util.concurrent.CopyOnWriteArrayList:写时复制 * 4. 优化建议(同样的错误不犯第2次) */ public class ContainerNotSafeDemo { public static void main(String[] args) { List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); },String.valueOf(i)).start(); } } } /** * 写时复制: * CopyOnWrite即写时复制的容器。忘一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将Object[]进行copy, * 复制出一个新的Object[] newElements,然后往新的Object[] newElements里添加元素,添加完元素后, * 在将原容器的引用指向新的容器 setArray(newElements)。这样做的好处可以对CopyOnWrite容器进行并发的读, * 而不需要加锁,因为当前元素不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写在不同的容器 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(); } } */
2. HashSet:
问题:
1. HashSet底层是什么?
创建了一个初始值是16,负载因子0.75的标准HashMap
2. 你确定HashSet的底层是HashMap吗?HashSet.add加值的时候加一个,而HashMap需要两个k-v键值对?
HashSet底层确实是HashMap,HashSet.add的时候确实调用的是map.put方法,add传入法人值就是map中的key,而value是Object的常量
package com.example.code.conllection; import java.util.HashSet; import java.util.Set; import java.util.UUID; /** * HashSet * 1. 故障现象:java.util.ConcurrentModificationException * 2. 导致原因: * 并发争抢导致,参考花名册签到,一个人正在写入,另一个同学过来抢夺,导致数据不一致,并发修改异常; * 3. 解决方案: * - Collections.synchronizedSet(new HashSet<>()); * - new CopyOnWriteArraySet<>():底层还是CopyOnWriteArrayList * 4. 优化建议(同样的错误不犯第2次) * */ public class CurrentSetDemo { public static void main(String[] args) { Set<String> list = new HashSet<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); },String.valueOf(i)).start(); } } }
3. HashMap:
问题:
1. HashMap的实现原理?
2. ConcurrentHashMap的特点?
package com.example.code.conllection; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * HashMap * 1. 故障现象:java.util.ConcurrentModificationException * 2. 导致原因: * 并发争抢导致,参考花名册签到,一个人正在写入,另一个同学过来抢夺,导致数据不一致,并发修改异常; * 3. 解决方案: * - Collections.synchronizedMap(new HashMap<>()); * - new ConcurrentHashMap<>(); * 4. 优化建议(同样的错误不犯第2次) * */ public class CurrentMapDemo { public static void main(String[] args) { Map<String, String> map = new ConcurrentHashMap<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8)); System.out.println(map); },String.valueOf(i)).start(); } } }