三种解决方法:Vector(效率低),Collections.synchronized...(效率也低),CopyOnWriteArrayList
public class UnSafeTest {
public static void main(String[] args) {
//List<String> list = new ArrayList<>();//方法一:vector是线程安全的但是性能低所以不使用
//方法二:使用工具类Collections这个集合类同时也能解决map,set的不安全问题
//List<String> list1 = Collections.synchronizedList(list);
//方法三:使用JUC的类CopyOnWriteArrayList
CopyOnWriteArrayList<Object> list1 = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//arrayList的线程不安全体现在add方法没有加锁
list1.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(list1);//发现并发修改异常java.util.ConcurrentModificationException
}, "" + i).start();
}
}
}
CopyOnWriteArrayList详解
public boolean add(E var1) {
ReentrantLock var2 = this.lock;
var2.lock();
boolean var6;
try {
Object[] var3 = this.getArray();//拿到数组
int var4 = var3.length;
Object[] var5 = Arrays.copyOf(var3, var4 + 1);//拷贝扩容
var5[var4] = var1;//写入新值
this.setArray(var5);//设置数组
var6 = true;//操作完毕
} finally {
var2.unlock();//释放锁
}
return var6;
}
CopyOnWrite也就是写时复制。往容器里添加一个元素的时候,不直接在当前容器Object[]添加,而是将当前容器Object[]进行复制,拷贝出一个新的容器后,再将原有的容器的引用指向新的容器,这样做的好处时可以对CopyOnWrite容器进行并发的读,读的时候不需要加锁,因为当前的容器不会添加任何的元素,所以CopyOnWrite也是一种读写分离的思想。
对于CopyOnWriteArraySet我们可以看到其底层也就是CopyOnWriteArrayList
类似于HashSet的底层是HashMap,但是HashSet只需要一个元素,HashMap需要两个元素,为什么呢?HashiSet的值其实就是HashMap的key,其value值为一个叫Present的常量。
同样的HashMap也是线程不安全的,但是在JUC中并不叫CopyOnWriteHashMap而是叫ConcurrentHashMap,其效果就是保证线程安全,这里不再赘述。需要注意的是ConcurrentHashMap的底层和HashMap底层实现有很大的区别
ConcurrentHashMap 由 Segment 数组结构 和 HashEntry 数组结构组成,一个ConcurrentHashMap 里面包含一个 Segment 数组(数组链表结构),一个 Segment 里面包含一个 HashEntry 数组 (链表结构),每个 Segment 守护一个HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得它对应的 Segment 锁,这个是java7之前的,在java8后又做了一些改动