请举例说明集合类是不安全的
一、如何证明线程是不安全的?
线程不安全错误:
java.util.ConcurrentModificationException
ArrayList在迭代的时候如果同时对其进行修改就会
抛出java.util.ConcurrentModificationException异常
并发修改异常
原理:
List<String> list = new ArrayList<>(); 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(); } 看ArrayList的源码 public boolean add( E e) {
ensureCapacityInternal( size + 1 );
// Increments modCount!!
elementData [ size ++] = e;
return true ;
} 没有 synchronized 线程不安全
二、解决方案
(1)vector
List<String> list = new Vector<>();  看Vector的源码 public synchronized boolean add( E e) {
modCount ++;
ensureCapacityHelper( elementCount + 1 );
elementData [ elementCount ++] = e;
return true ;
} 有 synchronized 线程安全
(2)Collections
List<String> list = Collections. synchronizedList ( new ArrayList<>()); Collections提供了方法 synchronizedList保证list是同步线程安全的 那HashMap,HashSet是线程安全的吗?也不是 所以有同样的线程安全方法
(3)写时复制
List<String> list = new CopyOnWriteArrayList<>();
不加锁性能提升出错误,加锁数据一致性能下降
【1】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等)都是通过生成底层数组的新副本来实现的。
【2】举例
【3】CopyOnWrite理论
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return { @code true} (as specified by { @link Collection#add}) */ 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(); } }
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
【4】扩展类比
HashSet
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 ;
}
PRESENT 一个常量
HashMap
Map<String,String> map = new HashMap<>();//线程不安全 Map<String,String> map = new ConcurrentHashMap<>();//线程安全
例子
package com.atguigu.gmall.jucdemo;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/** * 请举例说明集合类是不安全的 */
public class NotSafeDemo { 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(); }
} private static void setNoSafe() { Set<String> set = new CopyOnWriteArraySet<>(); for ( int i = 0 ; i < 30 ; i++) { new Thread(()->{ set .add(UUID. randomUUID ().toString().substring( 0 , 8 )); System. out .println( set ); },String. valueOf (i)).start(); }
} private static void listNoSafe() { // List<String> list = Arrays.asList("a","b","c"); // list.forEach(System.out::println); //写时复制 List<String> list = new CopyOnWriteArrayList<>(); // new CopyOnWriteArrayList<>(); //Collections.synchronizedList(new ArrayList<>()); //new Vector<>();//new ArrayList<>(); 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(); } } */