集合类不安全的问题

目录

1. ArrayList

1.1 介绍

1.2 ArrayList线程不安全的DEMO 

1.2.1 故障现象 

1.2.2 导致原因

1.2.3 解决方案

1.2.4 优化建议 JUC中CopyOnWriteArrayList解决问题的原理

2 Set

2.1 HashSet线程不安全

2.2 CopyOnWriteArraySet介绍

2.2.1 CopyOnWriteArraySet源码解析

2.3 HashSet源码解析

2.3.1 HashSet为什么add方法只有一个参数?

3 HashMap

3.1 HashMap是线程不安全的

3.2 ConcurrentHashMap

3.2.1 put源码

3.2.2 HashMap和ConcurrentHashMap的异同和区别?

3.2.3 JDK1.7到1.8 ConcurrentHashMap分段锁到锁优化的区别?

4 参考文献


1. ArrayList

1.1 介绍

HashSet底层数据结构是数组

new ArrayList : 创建了一个空的List, 初始值为10

集合类不安全的问题

追溯初始值是从哪里出现的

集合类不安全的问题

注意 boolean add()方法没有加锁, 是线程不安全的, 为了保证add的并发性效率,牺牲了安全性

集合类不安全的问题

​​​集合类不安全的问题

 集合类不安全的问题

扩容集合类不安全的问题

集合类不安全的问题

1.2 ArrayList线程不安全的DEMO 

/**
 * 集合类不安全问题
 * ArrayList
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i <= 3; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

输出:

集合类不安全的问题

1.2.1 故障现象 

java.util.ConcurrentModificationException非百分百出现异常

1.2.2 导致原因

并发争抢修改导致,A线程正在写入,B线程过来抢夺,导致数据不一致异常,即并发修改异常

举例:一个签名手册,A签名的过程被B打断,B争抢签名手册的时候导致A写错了,出现数据异常

1.2.3 解决方案

/**
 * 集合类不安全问题
 * ArrayList
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        //线程不安全集合
        List<String> list = new ArrayList<>();

        //解决线程不安全方案:
        //方案一 Vector底层是synchronized的集合
        //List<String> list = new Vector<>(); 
        //方案二 集合类中的线程的加锁方法
        //List<String> list = Collections.synchronizedList(new ArrayList<>());
        //方案三 JUC中的方法
        //List<String> list = new CopyOnWriteArrayList<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

方案一, 用锁

new ArrayList可改为new Vector, 数据一致性保证了,但并发性下降

集合类不安全的问题

方案二, 利用Collections

Collection 接口

Collections 集合接口类,里面也存在Map Set 等集合方法,可使用synchronizedList

集合类不安全的问题

方案三, 利用JUC中的类

集合类不安全的问题

1.2.4 优化建议 JUC中CopyOnWriteArrayList解决问题的原理

注意代码中的array是被volatile修饰的

集合类不安全的问题

下面是add增加元素的源码

    /**
     * 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); //复制并扩容+1
            newElements[len] = e; //赋值新增数据
            setArray(newElements); //更新回Array
            return true; //操作成功
        } finally {
            lock.unlock(); //释放锁
        }
    }

代码解析:

上述代码用的是: 写时复制 (读写分离的思想)

CopyOnWrite容器即写时复制的容器。

往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是:

1. 先将当前容器Object[]进行copy,复制出一个新的容器object[] newElements,

2. 然后新的容器object[] newElements 里添加元素,

3. 添加元素后,将原容器的引用指向新容器setArray(newElements);

这样做的好处是可以对CopyWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyWrite容器也是一种读写分离的思想,读和写不同的容器

2 Set

2.1 HashSet线程不安全

HashSet是线程不安全的,解决方式同ArrayList差不多

/**
 * 集合类不安全问题
 * HashSet
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        //线程不安全集合
        Set<String> set = new HashSet<>();

        //解决线程不安全方案:
        //方案一 集合类中的线程的加锁方法
        //Set<String> set = Collections.synchronizedSet(new HashSet<>());
        //方案二 JUC中的方法
        //Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

输出

集合类不安全的问题

2.2 CopyOnWriteArraySet介绍

集合类不安全的问题

2.2.1 CopyOnWriteArraySet源码解析

源码: 

集合类不安全的问题

注意 al = new CopyOnWriteArrayList<E>(); 底层还是用了CopyOnWriteArrayList;

2.3 HashSet源码解析

HashSet底层数据结构是HashMap,可查看源码 map = new HashMap<>();

new HashSet就是创建了初始值16 负载因子0.75的一个标准的HashMap

集合类不安全的问题

当使用HashSet<>().add(”“), map是kv键值对,add中为什么只有一个参数?

2.3.1 HashSet为什么add方法只有一个参数?

源码:

集合类不安全的问题

如上面的源码所述:

add中的参数被作为map的key来使用,value是一个常量,value是恒定的,并不被关心。

可查看上方2.3 HashSet源码解析中的代码 private static final Object PRESENT = new Object();

3 HashMap

3.1 HashMap是线程不安全的

和上面一样,同样的配方:

/**
 * 集合类不安全问题
 * ArrayList
 * HashSet
 * HashMap
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
//        arrayListNotSafe();
//        setNotSafe();
        mapNotSafe();
    }

    public static void mapNotSafe() {
        //线程不安全集合
        Map<String,String> map = new HashMap<>();

        //解决线程不安全方案:
        //方案一 集合类中的线程的加锁方法
        //Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
        //方案二 JUC中的方法
        //Map<String,String> map = new ConcurrentHashMap<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}

3.2 ConcurrentHashMap

集合类不安全的问题

3.2.1 put源码

/** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

待补充内容,学习后再完善

3.2.2 HashMap和ConcurrentHashMap的异同和区别?

3.2.3 JDK1.7到1.8 ConcurrentHashMap分段锁到锁优化的区别?

4 参考文献

Java面试_高频重点面试题 (第一、二、三季)_ 面试 第1、2、3季_柴林燕_周阳_哔哩哔哩_bilibili

上一篇:Set集合


下一篇:集合整理学习