引入
在多线程下,List ,Set ,Map 都是不安全的。
先拿List举个例子。
import java.util.*;
/**
* @ClassName ListTest
* @Description
* @Author SkySong
* @Date 2020-10-11 22:26
*/
@SuppressWarnings("ALL")
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {//开10条线程去修改list
new Thread(()->{
String uuid = UUID.randomUUID().toString().substring(0,5);
list.add(uuid);
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
用多个线程同时去修改一个 List 列表。
报了 并发修改异常,这一点是毋庸置疑的。
(java.util.ConcurrentModificationException 线程并发修改异常。)
解决方案
并发问题,首先想到的是 synchronize。
所以有了我们的第一个解决方案: new Vector()
import java.util.*;
/**
* @ClassName ListTest
* @Description
* @Author SkySong
* @Date 2020-10-11 22:26
*/
@SuppressWarnings("ALL")
public class ListTest {
public static void main(String[] args) {
List<String> list = new Vector<String>();
for (int i = 1; i <= 10; i++) {//开10条线程去修改list
new Thread(()->{
String uuid = UUID.randomUUID().toString().substring(0,5);
list.add(uuid);
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
来看看 Vector 的 addElement() 方法:
这是最传统的方法了。
我们还可以借助工具类 Collections(集合老大哥)
import java.util.*;
/**
* @ClassName ListTest
* @Description Collections
* @Author SkySong
* @Date 2020-10-11 22:26
*/
@SuppressWarnings("ALL")
public class ListTest {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i <= 10; i++) {//开10条线程去修改list
new Thread(()->{
String uuid = UUID.randomUUID().toString().substring(0,5);
list.add(uuid);
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
我们借助工具类 Collections 把 new 出来的 ArrayL<>() 变成一个安全的。
接下来便是我们的主角了:JUC解决方案——COW计算机优化策略
CopyOnWriteArrayList
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @ClassName ListTest
* @Description java.util.ConcurrentModificationException 线程并发修改异常。
* @Author SkySong
* @Date 2020-10-11 22:26
*/
@SuppressWarnings("ALL")
public class ListTest {
public static void main(String[] args) {
/**
* 解决线程并发问题:
* 1.List<String> list = new Vector<String>();
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {//开10条线程去修改list
new Thread(()->{
String uuid = UUID.randomUUID().toString().substring(0,5);
list.add(uuid);
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
来初步感受一下 CopyOnWriteArrayList 的设计思想:
从 CopyOnWriteArrayList 的 add() 方法中可以看出,他是将 数组资源 复制一版给调用者,调用者操作的是复制版,这样就避免了 多个线程共同操作一个 “母版” 的情况。
说了这么多,那么JUC方案的优势是什么呢?
CopyOnWriteArrayList 的 add() 方法避免使用了 synchronized ,而是使用了灵活性较高的 Lock 锁,在加上 COW 优化策略,使其效率有了很大程度的提高。
有了 List 当然 少不了 Set
CopyOnWriteArraySet
Set 和 List 都是 Collection老大哥的手下,所以很像。
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @ClassName SetTest
* @Description java.util.ConcurrentModificationException
* @Author SkySong
* @Date 2020-10-12 22:57
*/
@SuppressWarnings("ALL")
public class SetTest {
public static void main(String[] args) {
/**
* 解决方案:
* 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2.Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 10; i++) {//开10条线程去修改set
new Thread(()->{
String uuid = UUID.randomUUID().toString().substring(0,5);
set.add(uuid);
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
这里提了一下 HashSet ,简单聊聊:
其实 HashSet 底层就是个 HashMap。
- HashMap 的 key 是不允许重复的,有了这一点就不难联想了。
HashSet 的 add() 方法:
这里的 value 值就是一个静态常量,不用太在意。
List 和 Set 都说的了, 容器三剑客 怎么能少了 Map。
ConcurrentHashMap
在多线程并发的情况下 Map 也是不安全的。
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @ClassName MapTest
* @Description
* @Author SkySong
* @Date 2020-10-13 19:44
*/
public class MapTest {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName().toString(), UUID.randomUUID().toString());
System.out.println(map);
}).start();
}
}
}
同样会报如下异常:
这里补充一点HashMap的知识:
解决思路
这里当然可以用 Collections.synchronizedMap(new HashMap<>());
但这不是我们今天的主角。 JUC 才是我们今天的主角。
public static void main(String[] args) {
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName().toString(), UUID.randomUUID().toString());
System.out.println(map);
}).start();
}
}
关于 ConcurrentHashMap 的巧妙之处有很多。我们下次再聊!