我们知道ArrayList是线程不安全的,当高并发环境下如何兼顾性能和线程安全呢?
直接上代码:
/**
* @author: dada
* @date: 2020/12/18
* @description: 不安全的案例ArrayList
*/
public class NoSafeArrayListDemo {
public static void main(String[] args) {
//List<String> list = new ArrayList<>();//不安全
//List<String> list1 = new Vector<>();//性能太差,已经抛弃
//List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
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();
}
/**
* 1.new ArrayList<>()故障现象:
* java.util.ConcurrentModificationException
*
* 2.导致原因
* 并发争抢修改导致。
* 如:参考签到案例。
* 一个人正在签到,另一个人过来抢夺,导致数据不一致的异常。并发修改异常。
*
* 3.解决方案:
* 3.1 List<String> list1 = new Vector<>();
* 3.2 List<String> list2 = Collections.synchronizedList(new ArrayList<>());
* 3.3 List<String> list3 = new CopyOnWriteArrayList<>();
*
* 4.优化建议
* 使用new CopyOnWriteArrayList<>();
*
*/
}
}
ps:CopyOnWriteArrayList类的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();
}
}
add方法源码解析:
其实就是读写复制:
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往旧容器Object[] elements添加,而是先将当前容器Object[]复制出一个新Object[] newElements,然后往新的容器Object[] newElements添加元素后,再将原容器的引用指向新的容器setArray(newElements);
这样做的好处就是可以对CopyAndWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyAndWrite容器也是一种读写分离的思想,读和写在不同的容器。
--------------------------HashSet案例--------
public static void main(String[] args) {
//Set<String> set = new HashSet<>();//不安全
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
/**
* CopyOnWriteArraySet<>()的底层其实就是CopyOnWriteArrayList
* 看构其造函数:
* public CopyOnWriteArraySet() {
* al = new CopyOnWriteArrayList<E>();
* }
*/
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();
}
/**
* 1.Set<String> set = new HashSet<>();;//故障现象:
* java.util.ConcurrentModificationException
*
* 3.解决方案:
* 3.1 Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 3.2 Set<String> set = new CopyOnWriteArraySet<>();
* 4.优化建议
* 使用new CopyOnWriteArraySet<>();
*
*/
ps:补充HashSet添加原理:
HashSet源码底层是构造器也是调用HashMap
public HashSet() {
map = new HashMap<>();
}
其add方法
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
存的元素是map的key,而value值都是同一个值Object对象的常量值PRESENT。
--------------------HashMap案例---------------------------
public static void main(String[] args) {
//Map<String,String> map = new HashMap<>();//线程不安全
//Map<String,String> map = new ConcurrentHashMap<>();
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
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();
}
}