JUC并发编程(三)-- 集合线程安全
List
一、常用的Arraylist是否为线程安全
我们平时使用的List一般都为ArrayList,那么ArrayList是否是集合安全呢?我们来用代码实际测试一下:
package com.zhan.juc.safe;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Zhanzhan
* @Date 2020/11/29 15:06
* 并发下ArrayList安全吗?如果不安全,有什么解决方案?
*/
public class ListDemo {
public static void main(String[]args){
ListDemo listDemo = new ListDemo();
listDemo.testArrayList();
}
/**
* 测试普通的ArrayList
*/
public void testArrayList(){
testSafe(new ArrayList<>());
}
/**
* 测试当前的List集合是否线程安全
* @param list
*/
public void testSafe(List<String> list){
for (int i = 0;i<10;i++){
new Thread(()->{
list.add(String.valueOf(System.currentTimeMillis()));
System.out.println(list);
}, String.valueOf(i + 1)).start();
}
}
}
这里的主要业务逻辑就是给list添加元素,然后起十个线程来给同一个ArrayList添加,运行结果如下:
我们可以看到,抛出了java.util.ConcurrentModificationException异常,这个异常就是并发修改异常,所以我们可以得出结论:普通的ArrayList不是线程安全的。
二、解决方案
那么我们如何解决,才能让ArrayList变为线程安全呢?
1)使用 Collections 工具类来创建一个线程安全的 ArrayList
上代码:
package com.zhan.juc.safe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Author Zhanzhan
* @Date 2020/11/29 15:06
* 并发下ArrayList安全吗?如果不安全,有什么解决方案?
*/
public class ListDemo {
public static void main(String[]args){
ListDemo listDemo = new ListDemo();
// 测试普通的ArrayList是否为线程安全
// listDemo.testArrayList();
/**
* 解决方案
* 1、使用 Collections 工具类创建
*/
listDemo.testArrayListByCollections();
}
/**
* 使用Collections工具类创建一个同步List
*/
public void testArrayListByCollections(){
List<String> list = Collections.synchronizedList(new ArrayList<>());
testSafe(list);
}
/**
* 测试普通的ArrayList
*/
public void testArrayList(){
testSafe(new ArrayList<>());
}
/**
* 测试当前的List集合是否线程安全
* @param list
*/
public void testSafe(List<String> list){
for (int i = 0;i<10;i++){
new Thread(()->{
list.add(String.valueOf(System.currentTimeMillis()));
System.out.println(list);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果如下:
运行成功,说明线程安全。
原因分析:
我们这里看 Collections.synchronizedList的源码:
这里创建了一个SynchronizedList,然后继续看SynchronizedList的源码:
我们发现,SynchronizedList的add() ,是一个由synchronized修饰的同步代码块,所以会线程安全。
2)使用 CopyOnWriteArrayList
上代码:
package com.zhan.juc.safe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author Zhanzhan
* @Date 2020/11/29 15:06
* 并发下ArrayList安全吗?如果不安全,有什么解决方案?
*/
public class ListDemo {
public static void main(String[]args){
ListDemo listDemo = new ListDemo();
// 测试普通的ArrayList是否为线程安全
// listDemo.testArrayList();
/**
* 解决方案
* 1、使用 Collections 工具类创建
* 2、使用 CopyOnWriteArrayList 来创建
*/
// listDemo.testArrayListByCollections();
listDemo.testArrayListByCopyOnWrite();
}
/**
* <p>使用 CopyOnWriteArrayList 来创建。</p>
* <p>CopyOnWrite -- 写入时复制 ,是 COW 思想,就是计算机程序设计领域的一种优化策略。</p>
* <p>多个线程调用的时候,写或者添加元素时,会 copy 一份副本出来</p>
*
*/
public void testArrayListByCopyOnWrite(){
List<String> list = new CopyOnWriteArrayList<>();
testSafe(list);
}
/**
* 使用Collections工具类创建一个同步List
*/
public void testArrayListByCollections(){
List<String> list = Collections.synchronizedList(new ArrayList<>());
testSafe(list);
}
/**
* 测试普通的ArrayList
*/
public void testArrayList(){
testSafe(new ArrayList<>());
}
/**
* 测试当前的List集合是否线程安全
* @param list
*/
public void testSafe(List<String> list){
for (int i = 0;i<10;i++){
new Thread(()->{
list.add(String.valueOf(System.currentTimeMillis()));
System.out.println(list);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果:
运行成功,说明线程安全。
原因分析:
我们看CopyOnWriteArrayList的源码:
CopyOnWriteArrayList 比较适用于 读 多 写少 的场景。
因为 CopyOnWriteArrayList 在读时是不加锁的,只有在写时才会加锁,并且copy一份副本,比较占用内存,如果是大对象的写操作,可能会造成GC问题。
CopyOnWriteArrayList也只能保证数据的最终一致性,不能保证数据的实时一致性。
Set
一、常用的HashSet是否为线程安全?
废话不多说,直接上代码测试:
package com.zhan.juc.safe;
import java.util.HashSet;
import java.util.Set;
/**
* @Author Zhanzhan
* @Date 2020/11/29 16:07
* 并发下HashSet安全吗?如果不安全,有什么解决方案?
*/
public class SetDemo {
public static void main(String[] args) {
SetDemo setDemo = new SetDemo();
setDemo.testHashSet();
}
/**
* 测试普通的 HashSet
*/
public void testHashSet() {
testSafe(new HashSet<>());
}
/**
* 测试当前的 set 集合是否为线程安全
*
* @param set
*/
public void testSafe(Set<String> set) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(String.valueOf(System.currentTimeMillis()));
System.out.println(set);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果如下:
我们发现,又看到了熟悉的java.util.ConcurrentModificationException异常,说明HashSet不是线程安全的。
二、解决方案
那么如何才能使HashSet线程安全呢?
1) 使用 Collections 创建
上代码:
package com.zhan.juc.safe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @Author Zhanzhan
* @Date 2020/11/29 16:07
* 并发下HashSet安全吗?如果不安全,有什么解决方案?
*/
public class SetDemo {
public static void main(String[] args) {
SetDemo setDemo = new SetDemo();
// 测试普通的HashSet是否线程安全
// setDemo.testHashSet();
/**
* 解决方案
* 1、使用 Collections 工具类创建
* 2、使用 CopyOnWriteArraySet 来创建
*/
setDemo.testHashSetByCollections();
}
/**
* 测试普通的 HashSet
*/
public void testHashSet() {
testSafe(new HashSet<>());
}
public void testHashSetByCollections(){
Set<String> set = Collections.synchronizedSet(new HashSet<>());
testSafe(set);
}
/**
* 测试当前的 set 集合是否为线程安全
*
* @param set
*/
public void testSafe(Set<String> set) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(String.valueOf(System.currentTimeMillis()));
System.out.println(set);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果:
线程安全!
2)使用 CopyOnWriteArraySet创建
上代码:
package com.zhan.juc.safe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Author Zhanzhan
* @Date 2020/11/29 16:07
* 并发下HashSet安全吗?如果不安全,有什么解决方案?
*/
public class SetDemo {
public static void main(String[] args) {
SetDemo setDemo = new SetDemo();
// 测试普通的HashSet是否线程安全
// setDemo.testHashSet();
/**
* 解决方案
* 1、使用 Collections 工具类创建
* 2、使用 CopyOnWriteArraySet 来创建
*/
// setDemo.testHashSetByCollections();
setDemo.testHashSetByCopyOnWrite();
}
public void testHashSetByCopyOnWrite(){
Set<String> set = new CopyOnWriteArraySet<>();
testSafe(set);
}
/**
* 测试普通的 HashSet
*/
public void testHashSet() {
testSafe(new HashSet<>());
}
public void testHashSetByCollections(){
Set<String> set = Collections.synchronizedSet(new HashSet<>());
testSafe(set);
}
/**
* 测试当前的 set 集合是否为线程安全
*
* @param set
*/
public void testSafe(Set<String> set) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(String.valueOf(System.currentTimeMillis()));
System.out.println(set);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果:
线程安全!
CopyOnWriteArraySet和上面提到的CopyOnWriteArrayList一样。
Map
一、常用的HashMap是否为线程安全?
上代码:
package com.zhan.juc.safe;
import java.util.HashMap;
import java.util.Map;
/**
* @Author Zhanzhan
* @Date 2020/11/29 16:31
* 并发下HashMap安全吗?如果不安全,有什么解决方案?
*/
public class MapDemo {
public static void main(String[] args) {
MapDemo mapDemo = new MapDemo();
// 测试普通的 HashMap 是否为线程安全
mapDemo.testHashMap();
}
public void testHashMap() {
testSafe(new HashMap<>());
}
public void testSafe(Map<String, String> map) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
map.put(String.valueOf(System.currentTimeMillis()), "");
System.out.println(map);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果:
还是一样的配方,一样的味道,又出现了java.util.ConcurrentModificationException异常。
二、解决方案
1)使用 Collections 来创建同步的 HashMap
上代码:
package com.zhan.juc.safe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @Author Zhanzhan
* @Date 2020/11/29 16:31
* 并发下HashMap安全吗?如果不安全,有什么解决方案?
*/
public class MapDemo {
public static void main(String[] args) {
MapDemo mapDemo = new MapDemo();
// 测试普通的 HashMap 是否为线程安全
// mapDemo.testHashMap();
/**
* 解决方案
* 1、使用 Collections 工具类创建
*/
mapDemo.testHashMapByCollections();
}
/**
* 使用 Collections 来创建 同步的HashMap
*/
public void testHashMapByCollections() {
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
testSafe(map);
}
public void testHashMap() {
testSafe(new HashMap<>());
}
public void testSafe(Map<String, String> map) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
map.put(String.valueOf(System.currentTimeMillis()), "");
System.out.println(map);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果:
线程安全!
原因:
看源码:
使用Collections创建的 SynchronizedMap 的 put方法是用synchronized修饰的同步代码块,所以线程安全。
2)使用 ConcurrentHashMap 来创建
上代码:
package com.zhan.juc.safe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author Zhanzhan
* @Date 2020/11/29 16:31
* 并发下HashMap安全吗?如果不安全,有什么解决方案?
*/
public class MapDemo {
public static void main(String[] args) {
MapDemo mapDemo = new MapDemo();
// 测试普通的 HashMap 是否为线程安全
// mapDemo.testHashMap();
/**
* 解决方案
* 1、使用 Collections 工具类创建
* 2、使用 ConcurrentHashMap
*/
// mapDemo.testHashMapByCollections();
mapDemo.testHashMapByConcurrent();
}
/**
* 使用ConcurrentHashMap来创建线程安全的 HashMap
*/
public void testHashMapByConcurrent() {
Map<String, String> map = new ConcurrentHashMap<>();
testSafe(map);
}
/**
* 使用 Collections 来创建 同步的HashMap
*/
public void testHashMapByCollections() {
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
testSafe(map);
}
public void testHashMap() {
testSafe(new HashMap<>());
}
public void testSafe(Map<String, String> map) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
map.put(String.valueOf(System.currentTimeMillis()), "");
System.out.println(map);
}, String.valueOf(i + 1)).start();
}
}
}
运行结果:
线程安全!