|
| 1 | +package com.atguigu.interview.juc; |
| 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 | + * @author |
| 10 | + */ |
| 11 | +public class ContainerNotSafeDemo { |
| 12 | + public static void main(String[] args){ |
| 13 | + mapNotSafe(); |
| 14 | + } |
| 15 | + |
| 16 | + private static void mapNotSafe() { |
| 17 | + Map<String,String> map = new ConcurrentHashMap<>(); |
| 18 | + for (int i = 0; i < 30; i++) { |
| 19 | + new Thread(()->{ |
| 20 | + map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8)); |
| 21 | + System.out.println(map); |
| 22 | + },String.valueOf(i)).start(); |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + private static void setNotSafe() { |
| 27 | + Set<String> set = new CopyOnWriteArraySet<>(); |
| 28 | + for (int i = 0; i < 30; i++) { |
| 29 | + new Thread(()->{ |
| 30 | + set.add(UUID.randomUUID().toString().substring(0,8)); |
| 31 | + System.out.println(set); |
| 32 | + },String.valueOf(i)).start(); |
| 33 | + } |
| 34 | + // HashSet 与 HashMap |
| 35 | + new HashSet<String>().add("a"); |
| 36 | + } |
| 37 | + |
| 38 | + private static void listNotSafe() { |
| 39 | + List<String> list = new CopyOnWriteArrayList<>(); |
| 40 | + for (int i = 0; i < 30; i++) { |
| 41 | + new Thread(()->{ |
| 42 | + list.add(UUID.randomUUID().toString().substring(0,8)); |
| 43 | + System.out.println(list); |
| 44 | + },String.valueOf(i)).start(); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * 故障现场 |
| 49 | + * java.util.ConcurrentModificationException |
| 50 | + * 导致原因 |
| 51 | + * |
| 52 | + * 解决方案 |
| 53 | + * 1.new Vector<>(); |
| 54 | + * 2.Collections.synchronizedList(new ArrayList<>()); |
| 55 | + * 3.CopyOnWriteArrayList(); |
| 56 | + * CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候, |
| 57 | + * 不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容 |
| 58 | + * 器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们 |
| 59 | + * 可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。 |
| 60 | + * 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。 |
| 61 | + * CopyOnWrite的缺点 |
| 62 | + * 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存 |
| 63 | + * 里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容 |
| 64 | + * 器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用, |
| 65 | + * 所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再 |
| 66 | + * 写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。 |
| 67 | + * 之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的 |
| 68 | + * Full GC,应用响应时间也随之变长。 |
| 69 | + * 针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果 |
| 70 | + * 元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器, |
| 71 | + * 而使用其他的并发容器,如ConcurrentHashMap。 |
| 72 | + * |
| 73 | + * 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。 |
| 74 | + * 所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。 |
| 75 | + */} |
| 76 | +} |
0 commit comments