JAVA多线程之——CopyOnWriteArrayList

来源:互联网 发布:华西金手指软件下载 编辑:程序博客网 时间:2024/06/03 15:37

线程安全的list

在学list的时候,应该都了解过ArrayList是线程不安全的集合。今天学习线程安全的一个list集合。CopyOnWriteArrayList。
CopyOnWrite
从取名来看CopyOnWriteArrayList实质上就应该是个ArrayList。只是多了一个CopyOnWrite(写入时复制) 思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
好在了解了什么是写入时复制思想之后,再来看一下源码的具体实现:

/** The lock protecting all mutators */transient final ReentrantLock lock = new ReentrantLock();/** The array, accessed only via getArray/setArray. */private volatile transient Object[] array;

首先,CopyOnWriteArrayList类中定义了一个ReentrantLock锁,我们知道ReentrantLock是一个可重入的独占锁。 还定义了一个volatile类型的 array。 ArrayList的底层就是基于数组实现。
CopyOnWriteArrayList也也是一样。数组被定义为volatile。是因为volatile定义的变量在多线程环境中可以保证其可见性。声明ReentrantLock就是用来保证线程的安全性。先看读取:

@SuppressWarnings("unchecked")private E get(Object[] a, int index) {    return (E) a[index];}/*** {@inheritDoc}** @throws IndexOutOfBoundsException {@inheritDoc}*/public E get(int index) {    return get(getArray(), index);}

对于读取而言,很简单,就是读取数组的某一个元素。并没有上锁,这就意味着可以多个线程同时读。
添加/修改/删除

 public E set(int index, E element) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            E oldValue = get(elements, index);            if (oldValue != element) {                int len = elements.length;                Object[] newElements = Arrays.copyOf(elements, len);                newElements[index] = element;                setArray(newElements);            } else {                // Not quite a no-op; ensures volatile write semantics                setArray(elements);            }            return oldValue;        } finally {            lock.unlock();        }    }  public void add(int index, E element) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;            if (index > len || index < 0)                throw new IndexOutOfBoundsException("Index: "+index+                                                    ", Size: "+len);            Object[] newElements;            int numMoved = len - index;            if (numMoved == 0)                newElements = Arrays.copyOf(elements, len + 1);            else {                newElements = new Object[len + 1];                System.arraycopy(elements, 0, newElements, 0, index);                System.arraycopy(elements, index, newElements, index + 1,                                 numMoved);            }            newElements[index] = element;            setArray(newElements);        } finally {            lock.unlock();        }    } public E remove(int index) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;            E oldValue = get(elements, index);            int numMoved = len - index - 1;            if (numMoved == 0)                setArray(Arrays.copyOf(elements, len - 1));            else {                Object[] newElements = new Object[len - 1];                System.arraycopy(elements, 0, newElements, 0, index);                System.arraycopy(elements, index + 1, newElements, index,                                 numMoved);                setArray(newElements);            }            return oldValue;        } finally {            lock.unlock();        }    }

对于修改、删除、添加我们可以看到,都是先上独占锁,然后先把原来的数组复制一份,然后对复制出来的数组进行对应的操作,最后把新的数组赋值给volatile修饰的数组。因为数组是volatile修饰,这就保证了线程在修改之后,其它线程看到的一定是最新值。

缺点

  • 在进行修改、删除、添加的时候,都需要对整个数组进行复制,会占用两份内存。如果对象占用的内存较大,就会引发频繁的垃圾回收行为,降低性能;
  • CopyOnWrite只能保证数据最终的一致性,不能保证数据的实时一致性。volatile修饰的只保证可见性,不保证原子性。

总结
CopyOnWrite容器来说,只适合在读操作远远多于写操作的场景下使用

0 0
原创粉丝点击