深入Java集合系列之六:CopyOnWriteArrayList

标签: CopyOnWriteArrayList
3人阅读 评论(0) 收藏 举报
分类:

CopyOnWriteArrayList简介

CopyOnWriteArrayList容器是Collections.synchronizedList(List list)的替代方案,CopyOnWriteArrayList在某些情况下具有更好的性能,考虑读远大于写的场景,如果把所有的读操作进行加锁,因为只有一个读线程能够获得锁,所以其他的读线程都必须等待,大大影响性能。CopyOnWriteArrayList称为“写时复制”容器,就是在多线程操作容器对象时,把容器复制一份,这样在线程内部的修改就与其他线程无关了,而且这样设计可以做到不阻塞其他的读线程。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。

CopyOnWriteArrayList容器使用示例

下面的代码演示如何使用CopyOnWriteArrayList容器:

package com.rhwayfun.patchwork.concurrency.r0408;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by rhwayfun on 16-4-8.
 */
public class CopyOnWriteArrayListDdemo {

    /**
     * 内容编号
     */
    private static AtomicLong contentNum;

    /**
     * 日期格式器
     */
    private static DateFormat format;

    /**
     * 线程池
     */
    private final ExecutorService threadPool;

    public CopyOnWriteArrayListDdemo() {
        contentNum = new AtomicLong();
        format = new SimpleDateFormat("HH:mm:ss");
        threadPool = Executors.newFixedThreadPool(10);
    }

    public void doExec(int num) throws InterruptedException {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < num; i++){
            list.add(i,"main-content-" + i);
        }
        //5个写线程
        for (int i = 0; i < 5; i++){
            threadPool.execute(new Writer(list,i));
        }
        //启动10个读线程
        for (int i = 0; i < 10; i++){
            threadPool.execute(new Reader(list));
        }
        //关闭线程池
        threadPool.shutdown();
    }

    /**
     * 写线程
     *
     * @author rhwayfun
     */
    static class Writer implements Runnable {

        private final List<String> copyOnWriteArrayList;
        private int i;

        public Writer(List<String> copyOnWriteArrayList,int i) {
            this.copyOnWriteArrayList = copyOnWriteArrayList;
            this.i = i;
        }

        @Override
        public void run() {
            copyOnWriteArrayList.add(i,"content-" + contentNum.incrementAndGet());
            System.out.println(Thread.currentThread().getName() + ": write content-" + contentNum.get()
                    + " " +format.format(new Date()));
            System.out.println(Thread.currentThread().getName() + ": remove " + copyOnWriteArrayList.remove(i));
        }
    }

    static class Reader implements Runnable {

        private final List<String> list;

        public Reader(List<String> list) {
            this.list = list;
        }

        @Override
        public void run() {
            for (String s : list) {
                System.out.println(Thread.currentThread().getName() + ": read " + s
                        + " " +format.format(new Date()));
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayListDdemo demo = new CopyOnWriteArrayListDdemo();
        demo.doExec(5);
    }
}

首先启动5个写线程,再启动10个读线程,运行该程序发现并没有出现异常,所以使用写时复制容器效率很高。代码的运行结果如下:

部分输出

CopyOnWriteArrayList源码剖析

先说说CopyOnWriteArrayList容器的实现原理:简单地说,就是在需要对容器进行操作的时候,将容器拷贝一份,对容器的修改等操作都在容器的拷贝中进行,当操作结束,再把容器容器的拷贝指向原来的容器。这样设计的好处是实现了读写分离,并且读读不会发生阻塞。下面的源码是CopyOnWriteArrayList的add方法实现:

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;
            //1、复制出一个新的数组
            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);
            }
            //2、把新元素添加到新数组中
            newElements[index] = element;
            //3、把数组指向原来的数组
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

上面的三个步骤实现了写时复制的思想,在读数据的时候不会锁住list,因为写操作是在原来容器的拷贝上进行的。而且,可以看到,如果对容器拷贝修改的过程中又有新的读线程进来,那么读到的还是旧的数据。读的代码如下:

public E get(int index) {
        return get(getArray(), index);
    }
    final Object[] getArray() {
        return array;
    }

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景。

CopyOnWriteArrayList的缺点

从CopyOnWriteArrayList的实现原理可以看到因为在需要容器对象的时候进行拷贝,所以存在两个问题:内存占用问题数据一致性问题

内存占用问题:因为需要将原来的对象进行拷贝,这需要一定的开销。特别是当容器对象过大的时候,因为拷贝而占用的内存将增加一倍(原来驻留在内存的对象仍然在使用,拷贝之后就有两份对象在内存中,所以增加了一倍内存)。而且,在高并发的场景下,因为每个线程都拷贝一份对象在内存中,这种情况体现得更明显。由于JVM的优化机制,将会触发频繁的Young GC和Full GC,从而使整个系统的性能下降。

数据一致性问题:CopyOnWriteArrayList不能保证实时一致性,因为读线程在将引用重新指向原来的对象之前再次读到的数据是旧的,所以CopyOnWriteArrayList只能保证最终一致性。因此在需要实时一致性的厂几个CopyOnWriteArrayList是不能使用的。

CopyOnWriteArrayList小结:

  1. CopyOnWriteArrayList适用于读多写少的场景
  2. 在并发操作容器对象时不会抛出ConcurrentModificationException,并且返回的元素与迭代器创建时的元素是一致的
  3. 容器对象的复制需要一定的开销,如果对象占用内存过大,可能造成频繁的YoungGC和Full GC
  4. CopyOnWriteArrayList不能保证数据实时一致性,只能保证最终一致性
  5. 在需要并发操作List对象的时候优先使用CopyOnWriteArrayList


查看评论

深入Java集合学习系列:深入CopyOnWriteArraySet

http://www.cnblogs.com/skywang12345/p/3498497.html?utm_source=tuicool 概要 本章是JUC系列中的Cop...
  • lihui6636
  • lihui6636
  • 2015-10-07 11:11:05
  • 3335

深入Java集合学习系列(一)

HashMap的实现原理 HashMap 概述:HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序...
  • chengyunyi123
  • chengyunyi123
  • 2016-11-25 21:24:43
  • 177

Java集合的读写分离术--CopyOnWriteArrayList

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,...
  • wonderful_life_mrchi
  • wonderful_life_mrchi
  • 2017-08-16 16:18:04
  • 184

【深入理解java集合系列】ArrayList实现原理

1. ArrayList概述:    ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来...
  • xiaolei1021
  • xiaolei1021
  • 2016-08-15 22:57:56
  • 1857

深入Java集合学习系列(二):ArrayList实现原理

  • 2018年01月30日 11:27
  • 370KB
  • 下载

深入Java集合学习系列:LinkedList的实现原理

1. LinkedList概述:   List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头...
  • zheng0518
  • zheng0518
  • 2014-12-27 21:38:16
  • 6897

Java 集合深入理解(14):Map 概述

点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~ 终于把 List 常用的几种容器介绍完了,接下来开始 Map 的相关介绍。 什么是 MapJava 中的 Map 接口...
  • u011240877
  • u011240877
  • 2016-10-26 00:20:59
  • 4757

Java深入 - 深入理解Java集合

List类型 Java List一共三个实现类:分别是ArrayList、Vector和LinkedList。 类型 说明 ArrayList ArrayList是最常用的List实现类...
  • initphp
  • initphp
  • 2012-12-06 11:46:59
  • 2648

深入理解MySQL 5.7 GTID系列(六):MySQL启动初始化GTID模块

作者:高鹏(重庆八怪)原文地址:https://www.jianshu.com/p/fc836446cde0深入理解MySQL 5.7 GTID系列文章共十篇,本文为第四篇,第一篇:深入理解MySQL...
  • n88Lpo
  • n88Lpo
  • 2018-02-05 00:00:00
  • 132
    个人资料
    持之以恒
    等级:
    访问量: 7264
    积分: 782
    排名: 6万+
    轻松一下
    文章存档
    最新评论