Bootstrap

java安全性容器

 一、容器

·        在进行安全性容器的认识之前,先了解一下java中常见的几种容器。java中的容器在物理上可以分为俩种:Collection和Map。区别就是在存储时按对存储还是单值存储。以下是画的简略图:

二、并发容器

        主要是为之后的线程池打基础。

        我们常用的一些容器例如 ArrayList、HashMap、都不是线程安全的,在高并发情况下,会出现线程安全问题,此时就需要将这些容器变为线程安全的容器,最简单的方法是给这些容器所有的方法都加上 synchronized 关键字。这也是java最早期的并发容器以及Collections中各种容器的实现原理:

容器发展历程:
1:Vector Hashtable :早期使用,由synchronized实现,知道就行,基本不用 
2:ArrayList HashSet :此时考虑效率出现非同步容器
3:Collections.synchronized***:此时在Map或者List基础上实现了同步,工厂方法使用的也是synchronized,其实和HashTable区别就是,锁的粒度变小了
4:ConcurrentHashMap等:进行性能优化
5:这个不是说后边一定比前边好,要灵活应用

 1、Map型同步容器

  1)ConcurrentHashMap

        对于Map型来说平时用的最多的就是ConcurrentHashMap,也不是说他一定比HashTable和Collection.synchronizedMap好,其实你如果拿测试案例试一下,在写并发特别大的时候HashTable和Collection.synchronizedMap比ConcurrentHashMap效率更高,因为ConcurrentHashMap加锁方式相对来说比较复杂,加的是桶锁,而HashTable使用的是Hash表锁。

        ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。原来只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制),并发性的提升是显而易见的。
        而ConcurrentHashMap的读取并发才是真正体现其优势的地方,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(其实在锁粒度太多的时候也不是很好)。但是在求size等操作时也是需要锁定整个表。

        ConcurrentHashMap使用了不同于传统集合的快速失败迭代器,使用的是弱一致迭代器。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。

代码写一下迭代器,一会补充

        也可以看一下 ConcurrentHashMap中的各种实现方式。

2)HashTable

        后期补充,跳表。

2、队列型同步容器

1)CopyOnWriteList(写时复制)

        CopyOnWriteList即写时复制数组,就像它的名字一样,CopyOnWriteList在容器发生变更时,基于当前容器复制出一个新的容器,然后在新容器里变更,最后将旧容器的引用指向新容器,通过与ReentrantLock搭配实现线程安全。而对于容器的读是直接读取旧的指向是无锁操作。   

        以add方法进行举例,摘抄部分源码。

    //CopyOnWriteArrayList中两个成员变量,用来实现线程安全和防止指令重排    
    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    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;
            //是不是感觉这步有点多余
            //array 字段是被 volutile 修饰,所以调用 setArray() 方法会是缓存行内的 array 字段缓存失败,并防止指令重拍,即 happens-before 原理
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

    final void setArray(Object[] a) {
        array = a;
    }

        正如上面源码可以看出,在进行修改操作时,会复制一个新的底层数组,所以在读的时候,直接读取原array数组就行,就不需要加锁。读取效率非常高。

    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }

    //获取的是原数组
    final Object[] getArray() {
        return array;
    }

;