Java多线程详解(六)------常见集合的线程不安全实例(NoSafeDemo)

一、ArrayList线程不安全

 如下代码演示:

 1 package com.study.nosafedemo;
 2 
 3 import java.util.*;
 4 
 5 public class NoSafeDemo {
 6     public static void main(String[] args) {
 7         List<String> list = new ArrayList<>();
 8         for (int i = 0; i <= 30; i++) {
 9             new Thread(() -> {
10                 list.add(UUID.randomUUID().toString().substring(0, 8));
11                 System.out.println(list);
12             }, String.valueOf(i)).start();
13         }
14     }
15 }
16 
17 
18 java.util.ConcurrentModificationException

java.util.ConcurrentModificationException

ArrayList在迭代的时候如果同时对其进行修改就会
抛出java.util.ConcurrentModificationException异常
并发修改异常

看ArrayList的源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
没有synchronized线程不安全

二、解决方案:

1、List<String> list = new Vector<>();

看Vector的源码
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
有synchronized线程安全

2、List<String> list = Collections.synchronizedList(new ArrayList<>());
Collections提供了方法synchronizedList保证list是同步线程安全的

那HashMap,HashSet是线程安全的吗?也不是
所以有同样的线程安全方法

3、 写时复制

 List<String> list = new CopyOnWriteArrayList<>();

三、写时复制

不加锁性能提升出错误,加锁数据一致性但性能下降,怎么解决?

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.
CopyOnWriteArrayList是arraylist的一种线程安全变体,
其中所有可变操作(add、set等)都是通过生成底层数组的新副本来实现的。

 1  
 2 /**
 3  * Appends the specified element to the end of this list.
 4  *
 5  * @param e element to be appended to this list
 6  * @return {@code true} (as specified by {@link Collection#add})
 7  */
 8 public boolean add(E e) {
 9     final ReentrantLock lock = this.lock;
10     lock.lock();
11     try {
12         Object[] elements = getArray();
13         int len = elements.length;
14         Object[] newElements = Arrays.copyOf(elements, len + 1);
15         newElements[len] = e;
16         setArray(newElements);
17         return true;
18     } finally {
19         lock.unlock();
20     }
21 }
22  
23  
24  
25 CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
26 而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
27 添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
28 这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
29 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
30  
31  

扩展1:

Set<String> set = new HashSet<>();//线程不安全

Set<String> set = new CopyOnWriteArraySet<>();//线程安全

HashSet底层数据结构是什么?
HashMap ?

但HashSet的add是放一个值,而HashMap是放K、V键值对


public HashSet() {
map = new HashMap<>();
}

private static final Object PRESENT = new Object();

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

扩展2:

Map<String,String> map = new HashMap<>();//线程不安全

Map<String,String> map = new ConcurrentHashMap<>();//线程安全

代码:

 1 package com.atguigu.gmall.jucdemo;
 2 
 3 import java.util.*;
 4 import java.util.concurrent.ConcurrentHashMap;
 5 import java.util.concurrent.CopyOnWriteArrayList;
 6 import java.util.concurrent.CopyOnWriteArraySet;
 7 
 8 /**
 9  * 请举例说明集合类是不安全的
10  */
11 public class NotSafeDemo {
12     public static void main(String[] args) {
13 
14         Map<String,String> map = new ConcurrentHashMap<>();
15         for (int i = 0; i <30 ; i++) {
16             new Thread(()->{
17                 map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
18                 System.out.println(map);
19             },String.valueOf(i)).start();
20         }
21 
22 
23     }
24 
25     private static void setNoSafe() {
26         Set<String> set = new CopyOnWriteArraySet<>();
27         for (int i = 0; i <30 ; i++) {
28             new Thread(()->{
29                 set.add(UUID.randomUUID().toString().substring(0,8));
30                 System.out.println(set);
31             },String.valueOf(i)).start();
32         }
33     }
34 
35     private static void listNoSafe() {
36         //        List<String> list = Arrays.asList("a","b","c");
37         //        list.forEach(System.out::println);
38         //写时复制
39         List<String> list = new CopyOnWriteArrayList<>();
40         // new CopyOnWriteArrayList<>();
41         //Collections.synchronizedList(new ArrayList<>());
42         //new Vector<>();//new ArrayList<>();
43 
44         for (int i = 0; i <30 ; i++) {
45                     new Thread(()->{
46                         list.add(UUID.randomUUID().toString().substring(0,8));
47                         System.out.println(list);
48                     },String.valueOf(i)).start();
49                 }
50     }
51 
52 
53 }
54 
55 
56 
57 
58 
59     /**
60      * 写时复制
61      CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
62      而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
63      添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
64      这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
65      所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
66 
67      *
68      *
69      *
70      *
71 
72     public boolean add(E e) {
73         final ReentrantLock lock = this.lock;
74         lock.lock();
75         try {
76             Object[] elements = getArray();
77             int len = elements.length;
78             Object[] newElements = Arrays.copyOf(elements, len + 1);
79             newElements[len] = e;
80             setArray(newElements);
81             return true;
82         } finally {
83             lock.unlock();
84         }
85     }
86      */
原文地址:https://www.cnblogs.com/zsy-code/p/13781623.html