美文网首页
死磕Java——多线程下的集合

死磕Java——多线程下的集合

作者: Waldeinsamkeit4 | 来源:发表于2019-05-04 15:44 被阅读0次

    一、死磕Java——多线程下的集合

    1.1.ArrayList

    都知道ArrayList是线程不安全的,如果在多线程下使用了ArrayList 会产生什么样的情况,简单看一段代码。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
    
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
    

    代码很简单,就是三个线程向List集合中添加数据,多次运行不仅结果确多种多样,还会出现报错。

    [8b496, 686bd, a5bad]
    [8b496, 686bd, a5bad]
    [8b496, 686bd, a5bad]
    
    [null, c7fe4]
    [null, c7fe4]
    [null, c7fe4]
    
    [null, 17e5f, 4efd9]
    [null, 17e5f, 4efd9]
    [null, 17e5f, 4efd9]
    
    [null, null, 5b8ad]
    [null, null, 5b8ad]
    [null, null, 5b8ad]
    
    image-20190504131426678

    通过查看ArrayListadd方法源码就可以看到并没有加锁,所以在多线程环境下肯定会出现线程不安全问题,ArrayListadd方法源码如下:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    

    1.2.解决ConcurentModificationException

    当然要解决ConcurentModificationException这个问题,这个问题产生的原因就是在多线程下会出现争抢修改导致,即一个线程正在写入,另外一个线程抢去读,导致数据不一致异常。可以使用Vector或者使用List<String> list = Collections.synchronizedList(new ArrayList<>());来解决,但是,这两个都不是最好的解决办法,Vector是使用了synchronized关键字,使并发性下降,java.util.concurrent包下,有一个CopyOnWriteArrayList写时复制类可以解决这个问题,使用CopyOnWriteArrayList如下:

    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); // 拷贝一份扩容1
            newElements[len] = e; // 写入
            setArray(newElements); // 复制回去
            return true;
        } finally {
            lock.unlock(); // 释放锁
        }
    }
    

    1.3.写时复制的思想

    上面我们使用到了CopyOnWriteArrayList,也简单的看了一下源码,其中CopyOnWrite就是一个容器,写时复制的容器,往一个容器中添加一个新的元素的时候,不直接就添加进去当前容器,而是先将当前容器进行一个复制,往新的容器中添加一个元素,添加完元素后再将原容器的引用指向新的容器,当然,仅仅对写入操作进行加锁操作,同时可以对容器进行并发行的读,这就是读写分离的思想。

    1.4.HashSet

    HashSet也是线程不安全的,底层是HashMap,虽然底层是HashMapk-v键值对结构,但是HashSetadd方法却是可以只添加一个元素的,不是和HashMapk-v键值对结构冲突,而是源码中的v都是一样的,如下:

    public HashSet() {
        map = new HashMap<>();
    }
    
    public boolean add(E e) {
        return map.put(e, PRESENT)==null; // PRESENT是一个object类型的常量
    }
    

    当然,既然HashSet线程不安全,那么java.util.concurrent包,肯定也会有对应的类CopyOnWriteArraySet

    1.5.HashMap

    HashMap线程不安全是最常见的,至于为什么线程不安全,可以参看HashMap原码解读,当然java.util.concurrent包也少不了替代的类了,那就是ConcurrentHashMap

    相关文章

      网友评论

          本文标题:死磕Java——多线程下的集合

          本文链接:https://www.haomeiwen.com/subject/innxoqtx.html