Java JUC包源码分析 - CopyOnWriteArrayList和CopyOnWriteArraySet

CopyOnWriteArrayList可以看作是线程安全的ArrayList,底层数据结构是数组。主要实现过程就像它的名字,在写(增、删、改)的时候,先copy出一份副本,然后对副本操作,最后把副本赋值给原来的数组,整个过程是在ReentrantLock的lock下完成。

这么做的目的其实就是为了在保证线程安全的情况下,不阻塞读。

存储数据的是用volatile修饰的数组,能够保证多线程场景下能够读取到修改后的最新的数据。解释如下:

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。而我们在获取或者迭代的时候是没有加锁的,所以如果不加volatile修饰,可能各个线程看到的还是修改前的旧值。

在数组拷贝的过程中用到了大量的Arrays.copyof()方法,这个方法其实是调用了System.arraycopy()方法

其迭代器在迭代过程不支持remove,add,set

CopyOnWriteArraySet则是完全基于CopyOnWriteArrayList,其add方法是调用了CopyOnWriteArrayList的addIfAbsent()方法

看源码:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    // 可重入锁,保证写操作的线程安全
    final transient ReentrantLock lock = new ReentrantLock();

    // 数据存储结构:数组
    private transient volatile Object[] array;

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

    // 写入数组
    final void setArray(Object[] a) {
        array = a;
    }

    // 构造一个空的数组
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    // 构造一个已有的集合到这里的数组
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        // 如果传入的是CopyOnWriteArrayList类,那就直接把Collection强转然后获取数组
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            // c转换成数组
            elements = c.toArray();
            // toArray不返回Object[] 就需要拷贝元素到一个Object[]数组
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        // 设置数组
        setArray(elements);
    }

    // 放入一个元素
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 新建一个包含原数组内容且长度+1的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 数组的最后一个新加的长度那里就放置需要新加的值,原数组的引用指向新数组
            newElements[len] = e;
            setArray(newElements);
            return true;
        } 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;
            // 如果是需要插入到最后一个位置就直接向上面那个方法一样copy
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                // 先新建一个长度+1的数组,然后把原数组从index处拆两半copy到新数组
                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();
        }
    }
    
    // 获取数组a在索引index的值
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    // 获取数组索引处的值
    public E get(int index) {
        return get(getArray(), index);
    }

    // 移除索引位置的值
    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);
            // 数组长度变短一位,然后去掉索引处那个值,分两部分copy到新数组
            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();
        }
    }
    
    // 获取o的索引值
    private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                // 找数组里值为null的索引,为啥不写== o??
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }
    // 获取o的索引值
    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }

    // 插入一个不存在当前数组里面的元素,CopyOnWriteArraySet就是基于此实现的
    public boolean addIfAbsent(E e) {
        // 获取数组快照
        Object[] snapshot = getArray();
        // indexof如果找到了e的索引值,证明已经存在数组了,返回false
        // 否则插入数组
        // 注意做indexof没加锁,所以在indexof后其实可能这个元素已经被别的线程插入进去了
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            // 获取当前的数组
            Object[] current = getArray();
            int len = current.length;
            // 判断上面传入的快照和当期数组是否相等
            // 如果不想等,就说明在获取锁之前就有其他线程修改了数组
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                // 取两个数组长度的最小值
                int common = Math.min(snapshot.length, len); 
                // 遍历数组:这里其实分了两段遍历,for里面遍历[0,common],
                // indexof遍历[common,length],如果在前一段找到元素e了,就直接return false了
                // 不得不佩服能考虑到这种细节。
                for (int i = 0; i < common; i++)
                    // 如果在当前数组的[0,common]段中找到了e,说明真的在加锁前e被add进去了,
                    // 返回false。另外先判断新旧数组在index处相不相等,省去一些比较。
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                // 判断一下common到数组尾部是否存在e
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            // 把数组长度+1,新建数组并把元素插入进去返回true
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

    // 这个相等的比较方法也值得学习!null的比较也包含进去了
    private static boolean eq(Object o1, Object o2) {
        // 如果o1=null,o2=null,则返回true;
        // 如果o1=null,o2!=null,则返回false;
        // 如果o1!=null,则直接用equals方法比较
        return (o1 == null) ? o2 == null : o1.equals(o2);
    }
     
    // 获取迭代器,不支持remove,add,set
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    
    // 内部类
    static final 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;
        }

        public boolean hasPrevious() {
            return cursor > 0;
        }

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

        @SuppressWarnings("unchecked")
        public E previous() {
            if (! hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
        public void set(E e) {
            throw new UnsupportedOperationException();
        }
        public void add(E e) {
            throw new UnsupportedOperationException();
        }
    }

}

CopyOnWriteArraySet:

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 5457747651344034263L;

    private final CopyOnWriteArrayList<E> al;
    // 构造函数就是新建一个CopyOnWriteArrayList
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    // add方法也是调用CopyOnWriteArrayList的addIfAbsent
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
    // 其他方法也是调用CopyOnWriteArrayList的方法......
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的JUCjava.util.concurrent)提供了一些并发编程中常用的类,这些类可以帮助我们更方便地实现多线程编程。以下是一些常用的JUC类及其解析: 1. CountDownLatch(倒计时器) CountDownLatch是一个计数器,它允许一个或多个线程等待一组事件发生后再继续执行。它最基本的方法是await()和countDown()。await()方法会阻塞当前线程,直到计数器的值为0;countDown()方法会将计数器的值减1。 2. CyclicBarrier(循环屏障) CyclicBarrier是一个同步工具,它允许一组线程等待彼此达到一个公共屏障点。当所有线程都到达这个屏障点时,它们才能继续执行。CyclicBarrier可以被重复使用,当所有线程都执行完后,它会自动重置。 3. Semaphore(信号量) Semaphore是一种计数器,它维护了一组许可证。当调用acquire()方法时,线程会阻塞,直到许可证可用;而当调用release()方法时,许可证的数量会增加。Semaphore可以用于限制同时访问某些资源的线程数量。 4. ReentrantLock(重入锁) ReentrantLock是一个可重入的互斥锁。它和synchronized关键字类似,但是提供了更多的灵活性和功能。ReentrantLock中最常用的方法是lock()和unlock(),它们分别用于获取锁和释放锁。 5. ConcurrentHashMap(并发哈希表) ConcurrentHashMap是一个线程安全的哈希表实现。它和HashMap类似,但是支持并发访问。ConcurrentHashMap中的所有方法都是线程安全的,而且它的性能比Hashtable和同步的HashMap要好。 6. Executors(线程池) Executors是一个工厂类,用于创建各种类型的线程池。它提供了一些静态方法,例如newFixedThreadPool()、newCachedThreadPool()、newSingleThreadExecutor()等,可以方便地创建各种类型的线程池。 7. Future(异步计算) Future是一个接口,它表示一个异步计算的结果。Future可以通过get()方法获取计算结果,或者通过cancel()方法取消计算。Future还可以用于实现一些高级的并发操作,例如等待一组异步计算全部完成后再继续执行。 总的来说,JUC提供了很多有用的类和工具,可以帮助我们更方便地实现多线程编程,提高程序的并发性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值