NotSafeDemo

    科技2022-07-15  127

    NotSafeDemo

    需求线程不安全的错误原理解决方案 例子ListNotSafe()SetNotSafe()MapNotSafe()

    需求

    请举例说明集合类是不安全的

    线程不安全的错误

    java.util.ConcurrentModificationException

    ArrayList在迭代的时候如果同时对其进行修改就会抛出并发修改异常.

    原理

    看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是同步线程安全的

    3、写时复制

    List<String> list = new CopyOnWriteArrayList<>(); 看看源码: 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容器也是一种读写分离的思想,读和写不同的容器。

    例子

    ListNotSafe()

    package cduck.cn; /** * ArrayList 有序可重复 * HashSet、HashMap 无序不可重复 * * ”一有则全有,一无则全无“ */ 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) { public static void ListNotSafe() { /** * ArrayList()线程不安全会出现ConcurrentModificationException * * 解决:1、用Vector<>(); 虽然能保证数据一致性,但是访问效率低下 * 2、Collections.synchronizedList(); * 3、new CopyOnWriteArrayList();只锁住你的写操作,同时允许别人读 */ List<String> list= new CopyOnWriteArrayList();//Collections.synchronizedList(new ArrayList<>()); /** * 看它的源码: * 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(); * } * } * * 相当于有一个签到表,现在有一部分人已经签到了,然后A来了,要签到,他把这个签名表 * 复制了一份拿走去签名(每次扩容一个位置--就是自己的签名位置👉对应了源码中的“len + 1”),然后 * 原版本1.0,就粘贴在墙上可以让别人观看,但是别人不能写。当他写完了,通知下一个人B,B同样Copy一份 * 1.1版本(A已经签名了的)去写,然后把另一张1.1版本贴在墙上供别人观看。 * */ for (int i=0;i<30;i++){ new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8));//给list中加入随机元素 System.out.println(list); },String.valueOf(i)).start(); } } }

    SetNotSafe()

    public static void SetNotSafe() { /** * 问题同List * * 解决方法: * 1、Collections.synchronizedSet(new HashSet<>()); * 2、new CopyOnWriteArraySet<>();--JUC下的! * * *HashSet的底层实现是HashMap,但是明明Map是K,V对,是两个值,HashSet是一个值,为什么说底层实现是HashMap呢? * * 答:先来看HashSet的部分源码 * public boolean add(E e) { * return map.put(e, PRESENT)==null; * } * 可以看到,HashSet存的是Map的Key,而本该存Value的地方存放的是一个PRESENT常量,所以可以看作是只存了一个K。 * */ Set<String> set=new CopyOnWriteArraySet<>(); //Collections.synchronizedSet(new HashSet<>()); for (int i=0;i<30;i++){ new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,8));//给set中加入随机元素 System.out.println(set); },String.valueOf(i)).start(); } }

    MapNotSafe()

    public static void MapNotSafe() { /** * 问题 同List * * 解决: * 1、Collections.synchronizedMap(new HashMap<>()); * 2、new ConcurrentHashMap<>();--JUC中的! */ Map<String,String> map=new ConcurrentHashMap<>(); //Collections.synchronizedMap(new HashMap<>()); //new HashMap(); // Map<String,String> map2= new HashMap<>(16);查看API可以发现MAP默认长度是16,负载因子是0.75; // 如果你可以明确的知道MAP中存放的东西大小是多少,比如一直都是100多条电话号码,你可以直接把Map的size设置为200,省去了之后扩容的时间 for (int i=0;i<30;i++){ new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));//给map中加入随机元素 System.out.println(map); },String.valueOf(i)).start(); } /** * map中其实存放的是一个个node,node中有key,value * * 读一小段map源码 * static class Node<K,V> implements Map.Entry<K,V> { * final int hash; * final K key; * V value; * Node<K,V> next; * * Node(int hash, K key, V value, Node<K,V> next) { * this.hash = hash; * this.key = key; * this.value = value; * this.next = next; * } * ......... * } * */}
    Processed: 0.011, SQL: 8