Java面试之线程安全集合

本文涉及到的集合均继承于Collection接口,此篇不讨论继承于Map的集合对象

既然本文讲的是线程安全的集合,那么开门见山。Java中目前能基本保证线程安全集合的操作方式有如下三种:Vector对象、Collections.SynchronizedList对象、CopyOnWriteArrayList集合对象

Vector对象:线程安全机制为使用synchronized关键字对方法进行加锁,属于爷爷辈的线程安全对象了。从下面的源码可以知道底层为数组,初始大小为10。扩容机制为达到最大容量之后进行原数组容量*2的扩容,扩容因子为1,最大容量为Integer.MAX_VALUE。万幸的是Vector类的iterator方法为线程安全方法

    /**
     * The array buffer into which the components of the vector are
     * stored. The capacity of the vector is the length of this array buffer,
     * and is at least large enough to contain all the vector's elements.
     *
     * <p>Any array elements following the last element in the Vector are null.
     *
     * @serial
     */
    protected Object[] elementData;

    /**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero.
     */
    public Vector() {
        this(10);
    }


    //下面为扩容源码的执行链
    /**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        elementCount = s + 1;
    }

    private Object[] grow() {
        return grow(elementCount + 1);
    }

    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }


从下面的继承图可以发现Vector对象继承了Cloneable、Serializable、RandomAccess接口,代表Vector支持拷贝、序列化、随机访问功能

Collections.SynchronizedList对象:个人认为它更像一个线程安全的容器,从这个对象的构造方法可以感知一二

而 SynchronizedList能够保证线程安全也是通过父类SynchronizedCollection,父类构造方法如下。mutex就是在这个容器中保证线程安全的锁对象。大致逻辑就是通过SynchronizedCollection中设置mutex对象,对mutex加锁后再调用List<E> list的相关方法来保证线程安全。

但是有一点需要注意,同样也在源码中列举出来了,就是咱们的iterator方法是没有对mutex加锁的。所以在调用迭代器进行for循环读取前记得用synchronized关键将参数list锁住。

//SynchronizedCollection构造逻辑
final Object mutex;     // Object on which to synchronize

SynchronizedCollection(Collection<E> c) {
    this.c = Objects.requireNonNull(c);
    mutex = this;
}
//SynchronizedCollection构造逻辑结束

//举两个SynchronizedCollection源码例子
public boolean add(E e) {
    synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
    synchronized (mutex) {return c.remove(o);}
}

//SynchronizedList的迭代器方法
public ListIterator<E> listIterator() {
    return list.listIterator(); // Must be manually synched by user
}

public ListIterator<E> listIterator(int index) {
    return list.listIterator(index); // Must be manually synched by user
}

CopyOnWriteArrayList对象:

比较一下和ArrayList继承树的区别,说实话没啥区别

那 CopyOnWriteArrayList对象怎么去进行线程同步就需要从源码找答案,如下:

可以发现 CopyOnWriteArrayList构建了一个lock对象和array对象数组,同步也是从这一块开始的。首先CopyOnWriteArrayList的方法通过synchronized关键字锁住lock对象,对调用对象进行上锁(对象锁),保证多线程的原子性。同时对象数组array被关键字volatile修饰,代表在读取array的时候直接读取内存中的值,保证多线程的可见性。通过上述两方面进行线程安全的控制,顺带多一句嘴,这个类适合查询多于修改、删除的业务场景。原因在于:在改删场景使用的方法是通过clone来新建对象数组赋值给array,这样对于内存是一种损耗。

    /**
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     *
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this list.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     *
     * @return an array containing all the elements in this list
     */
    public Object[] toArray() {
        return getArray().clone();
    }

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        synchronized (lock) {
            Object[] es = getArray();
            E oldValue = elementAt(es, index);

            if (oldValue != element) {
                es = es.clone();
                es[index] = element;
            }
            // Ensure volatile write semantics even when oldvalue == element
            setArray(es);
            return oldValue;
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_42505381/article/details/128246039