一、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 */