美文网首页
List用哪个?ArrayList -> Vector -> S

List用哪个?ArrayList -> Vector -> S

作者: wyn_做自己 | 来源:发表于2021-12-03 16:21 被阅读0次

今日份鸡汤:那些你暂时不能战胜的,不能克服的,不能容忍的,不能宽容的,就告诉自己,凡是不能杀死你的,最终都会让你更强~

选择哪个其实就看什么场景,说白了,你不需要考虑线程安全问题,那就直接用ArrayList呗,不加锁,效率高,但是遇到多线程时,你就要考虑线程安全的问题了,来看看下面的code:

public class ListDemo {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

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

很简单的一段代码,但是再来看看结果:

Exception in thread "28" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at java.util.AbstractCollection.toString(AbstractCollection.java:461)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.wyn.demo.ListDemo.lambda$main$0(ListDemo.java:14)
    at java.lang.Thread.run(Thread.java:748)

那么如何解决呢?
我们比较熟悉的是使用 Vector
先来看看二者源码对比

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

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
 Vector源码:
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

非常明显的区别, Vector add方法加了synchronized锁。而List没有。

Vector 加了方法级别的锁,性能比较差,要是不用他还可以用哪个呢?
使用集合工具类 Collections.synchronizedList(new ArrayList<>()) 就可以创建一个安全的list,看看源码是怎么实现的

Collections.synchronizedList源码:
        public boolean add(T e) {
            synchronized(mutex) {
                return backingList.add(e);
            }
        }

        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }

从这个实现上来看不能看出,他的性能也并不是那么高,而我们工作场景中有很多读多写少的场景,有没有适合这种场景的,并且能保证线程安全呢?
答案一定是肯定的,介绍两个并发包里面的并发集合类:

java.util.concurrent.CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArraySet

CopyOnWrite集合类也就这两个,Java 1.5 开始加入 ,你值得拥有,采用了读写分离思想,写时复制出来一份副本,读不加锁,这时读的还是原版本的数据,而写则是往copy出来的副本写,当写完后将指针指向这个副本,读多写少的场景建议使用他,毕竟效率高嘛,但是如果写多读少,使用这个就没意义了,因为每次写操作都要进行集合内存复制,性能开销很大,如果集合较大,很容易造成内存溢出。

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();
    }
}

    public E get(int index) {
        return get(getArray(), index);
    }

CopyOnWriteArraySet
CopyOnWriteArraySet逻辑就更简单了,就是使用 CopyOnWriteArrayList 的 addIfAbsent 方法来去重的,添加元素的时候判断对象是否已经存在,不存在才添加进集合。

CopyOnWriteArraySet源码:
    /**
     * Appends the element, if not present.
     *
     * @param e element to be added to this list, if absent
     * @return {@code true} if the element was added
     */
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

同理map 和 set 也存在不安全问题呢,解决方式也类似哦,好了,这篇就到这了。

相关文章

网友评论

      本文标题:List用哪个?ArrayList -> Vector -> S

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