第三章 CopyOnWriteArrayList源码解析

注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析。

http://www.cnblogs.com/java-zhao/p/5102342.html

1、对于CopyOnWriteArrayList需要掌握以下几点

  • 创建:CopyOnWriteArrayList()
  • 添加元素:即add(E)方法
  • 获取单个对象:即get(int)方法
  • 删除对象:即remove(E)方法
  • 遍历所有对象:即iterator(),在实际中更常用的是增强型的for循环去做遍历

注:CopyOnWriteArrayList是一个线程安全,读操作时无锁的ArrayList。

2、创建

public CopyOnWriteArrayList()

使用方法:

List<String> list = new CopyOnWriteArrayList<String>();

相关源代码:

    private volatile transient Object[] array;//底层数据结构

    /**
     * 获取array
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * 设置Object[]
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * 创建一个CopyOnWriteArrayList
     * 注意:创建了一个0个元素的数组
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

注意点:

  • 设置一个容量为0的Object[];ArrayList会创造一个容量为10的Object[]

3、添加元素

public boolean add(E e)

使用方法:

list.add("hello");

源代码:

    /**
     * 在数组末尾添加元素
     * 1)获取锁
     * 2)上锁
     * 3)获取旧数组及其长度
     * 4)创建新数组,容量为旧数组长度+1,将旧数组拷贝到新数组
     * 5)将要增加的元素加入到新数组的末尾,设置全局array为新数组
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁
        lock.lock();//上锁
        try {
            Object[] elements = getArray();//获取当前的数组
            int len = elements.length;//获取当前数组元素
            /*
             * Arrays.copyOf(elements, len + 1)的大致执行流程:
             * 1)创建新数组,容量为len+1,
             * 2)将旧数组elements拷贝到新数组,
             * 3)返回新数组
             */
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;//新数组的末尾元素设成e
            setArray(newElements);//设置全局array为新数组
            return true;
        } finally {
            lock.unlock();//解锁
        }
    }

注意点:

  • Arrays.copyOf(T[] original, int newLength)该方法在ArrayList中讲解过

疑问:

  • 在add(E)方法中,为什么要重新定义一个ReentrantLock,而不直接使用那个定义的类变量锁(全局锁)

    • 答:我们在add(E)中加锁,是为了不能让两个线程同时执行add(E)方法,所以给add加了锁;但是,我们可以同时有两个线程一个做add操作,一个做remove操作,如果使用全局锁的话,那么add与remove同一时刻就只能有一个执行了。原因是这样吗?("同时有两个线程一个做add操作,一个做remove操作"这个可以吗?如果可以的话,那么在假设在add操作刚刚获取完原数组的长度之后,remove操作整好将原数组长度减1了,这个时候,add之后实际创建出来的数组就比原数组大2了,那么下次add的时候就可以放下这个元素了,为什么还要扩容呢?)(懂这一块儿的哥们儿给指点一下)
  • 根据以上代码可知,每增加一个新元素,都要进行一次数组的复制消耗,那为什么每次不将数组的元素设大(比如说像ArrayList那样,设置为原来的1.5倍+1),这样就会大大减少因为数组元素复制所带来的消耗?

4、获取元素

public E get(int index)

使用方法:

list.get(0)

源代码:

    /**
     * 根据下标获取元素
     * 1)获取数组array
     * 2)根据索引获取元素
     */
    public E get(int index) {
        return (E) (getArray()[index]);
    }

注意点:

  • 获取不需要加锁

疑问:读操作会发生脏读,为什么?

5、删除元素

public boolean remove(Object o)

使用方法:

list.remove("hello")

源代码:

    /**
     * 删除list中的第一个o
     * 1)获取锁、上锁
     * 2)获取旧数组、旧数组的长度len
     * 3)如果旧数组长度为0,返回false
     * 4)如果旧数组有值,创建新数组,容量为len-1
     * 5)从0开始遍历数组中除了最后一个元素的所有元素
     * 5.1)将旧数组中将被删除元素之前的元素复制到新数组中,
     * 5.2)将旧数组中将被删除元素之后的元素复制到新数组中
     * 5.3)将新数组赋给全局array
     * 6)如果是旧数组的最后一个元素要被删除,则
     * 6.1)将旧数组中将被删除元素之前的元素复制到新数组中
     * 6.2)将新数组赋给全局array
     */
    public boolean remove(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();//获取原数组
            int len = elements.length;//获取原数组长度
            if (len != 0) {//如果有数据
                // Copy while searching for element to remove
                // This wins in the normal case of element being present
                int newlen = len - 1;//新数组长度为原数组长度-1
                Object[] newElements = new Object[newlen];//创建新数组

                for (int i = 0; i < newlen; ++i) {//遍历新数组(不包含最后一个元素)
                    if (eq(o, elements[i])) {
                        // 将旧数组中将被删除元素之后的元素复制到新数组中
                        for (int k = i + 1; k < len; ++k)
                            newElements[k - 1] = elements[k];
                        setArray(newElements);//将新数组赋给全局array
                        return true;
                    } else
                        newElements[i] = elements[i];//将旧数组中将被删除元素之前的元素复制到新数组中
                }

                if (eq(o, elements[newlen])) {//将要删除的元素时旧数组中的最后一个元素
                    setArray(newElements);
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

判断两个对象是否相等:

    /**
     * 判断o1与o2是否相等
     */
    private static boolean eq(Object o1, Object o2) {
        return (o1 == null ? o2 == null : o1.equals(o2));
    }

注意点:

  • 需要加锁
  • ArrayList的remove使用了System.arraycopy(这是一个native方法),而这里没使用,所以理论上这里的remove的性能要比ArrayList的remove要低

6、遍历所有元素

iterator()  hasNext()  next()

使用方法:

讲解用的:

        Iterator<String> itr = list.iterator();
        while(itr.hasNext()){
            System.out.println(itr.next());
        }

实际中使用的:

        for(String str : list){
            System.out.println(str);
        }

源代码:

    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

    private static class COWIterator<E> implements ListIterator<E> {
        private final Object[] snapshot;//数组快照
        private int cursor;//可看做数组索引

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;//将实际数组赋给数组快照
        }

        public boolean hasNext() {
            return cursor < snapshot.length;//0~snapshot.length-1
        }

        public E next() {
            if (!hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

说明:这一块儿代码非常简单,看看代码注释就好。

注意:

由于遍历的只是全局数组的一个副本,即使全局数组发生了增删改变化,副本也不会变化,所以不会发生并发异常。但是,可能在遍历的过程中读到一些刚刚被删除的对象。

注意点:

总结:

  • 线程安全,读操作时无锁的ArrayList
  • 底层数据结构是一个Object[],初始容量为0,之后每增加一个元素,容量+1,数组复制一遍
  • 增删改上锁、读不上锁
  • 遍历过程由于遍历的只是全局数组的一个副本,即使全局数组发生了增删改变化,副本也不会变化,所以不会发生并发异常
  • 读多写少且脏数据影响不大的并发情况下,选择CopyOnWriteArrayList

疑问:

  • 在add(E)方法中,为什么要重新定义一个ReentrantLock,而不直接使用那个定义的类变量锁(全局锁)
  • 每增加一个新元素,都要进行一次数组的复制消耗,那为什么每次不将数组的元素设大(比如说像ArrayList那样,设置为原来的1.5倍+1),这样就会大大减少因为数组元素复制所带来的消耗?
  • get(int)操作会发生脏读,为什么?
时间: 2024-11-04 08:25:26

第三章 CopyOnWriteArrayList源码解析的相关文章

第六章 ReentrantLock源码解析2--释放锁unlock()

最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final ReentrantLock lock = new ReentrantLock(); lock.lock();//获取锁 try { a++;//业务逻辑 } catch (Exception e) { }finally{ lock.unlock();//释放锁 } 注:关于lock()方法的源码解析,请参照"第五章

第十四章 Executors源码解析

前边两章介绍了基础线程池ThreadPoolExecutor的使用方式.工作机理.参数详细介绍以及核心源码解析. 具体的介绍请参照: 第十二章 ThreadPoolExecutor使用与工作机理 第十三章 ThreadPoolExecutor源码解析 1.Executors与ThreadPoolExecutor ThreadPoolExecutor 可以灵活的自定义的创建线程池,可定制性很高 想创建好一个合适的线程池比较难 使用稍微麻烦一些 实际中很少使用 Executors 可以创建4种线程池

第四章 CopyOnWriteArraySet源码解析

注:在看这篇文章之前,如果对CopyOnWriteArrayList底层不清楚的话,建议先去看看CopyOnWriteArrayList源码解析. http://www.cnblogs.com/java-zhao/p/5121944.html 1.对于CopyOnWriteArraySet需要掌握以下几点 创建:CopyOnWriteArraySet() 添加元素:即add(E)方法 删除对象:即remove(E)方法 遍历所有对象:即iterator(),在实际中更常用的是增强型的for循环去

第六章 HashSet源码解析

6.1.对于HashSet需要掌握以下几点 HashSet的创建:HashSet() 往HashSet中添加单个对象:即add(E)方法 删除HashSet中的对象:即remove(Object key)方法 判断对象是否存在于HashSet中:containsKey(Object key)  注:HashSet没有获取单个对象的方法,需要使用iterator 6.2.构建HashSet 源代码: //HashSet底层数据结构:通过hashmap的key不可重复的原则,使得存放入HashSet

(三)Mina源码解析之IoFilter

本篇文章主要剖析Mina中的过滤器是如何实现的 首先还是引入一个简单的完整的server端的例子,代码如下 public class Server { public static void main(String[] args) { IoAcceptor acceptor = new NioSocketAcceptor(); acceptor.getSessionConfig().setReadBufferSize(2048); acceptor.getSessionConfig().setId

ArrayList.add() 方法吉林快-三平台出租源码解析

吉林快-三平台出租Q1446595067解析源码的方法>>> list.add("hello"); 该方法涉及到的方法被我复制到了一个类中,至于解释,全在注释上.初次解析,别喷我!!! 如有不足望评论,随时补充. package com.nc.sourceCode; import java.lang.reflect.Array;import java.util.ArrayList;import java.util.Arrays;import java.util.Lis

第二章 ArrayList源码解析

一.对于ArrayList需要掌握的七点内容 ArrayList的创建:即构造器 往ArrayList中添加对象:即add(E)方法 获取ArrayList中的单个对象:即get(int index)方法 删除ArrayList中的对象:即remove(E)方法 遍历ArrayList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历 判断对象是否存在于ArrayList中:contain(E) ArrayList中对象的排序:主要取决于所采取的排序算法(以后讲) 二.源

CopyOnWriteArrayList源码解析

CopyOnWriteArrayList是java1.5版本提供的一个线程安全的ArrayList变体. 在讲解5.1.1ArrayList的时候,有说明ArrayList的fail-fast特性,它是指在遍历过程中,如果ArrayList内容发生过修改,会抛出ConcurrentModificationException. 在多线程环境下,这种情况变得尤为突出,参见测试代码(代码基于java8): ArrayList<Integer> list = new ArrayList<>

第九章 LinkedBlockingQueue源码解析

1.对于LinkedBlockingQueue需要掌握以下几点 创建 入队(添加元素) 出队(删除元素) 2.创建 Node节点内部类与LinkedBlockingQueue的一些属性 static class Node<E> { E item;//节点封装的数据 /** * One of: * - the real successor Node * - this Node, meaning the successor is head.next * - null, meaning there