CopyOnWriteArrayList

今天来复习一下集合。在支持并发的集合中,我觉得CopyOnWriteArrayList是相对容易理解的一个。
CopyOnWrite:写时复制,就是当有线程向集合添加元素时,不是直接往旧的容器中添加元素,而是将旧的容器中的元素复制到新的容器中,读的时候读的仍然是旧容器,这样就不会影响并发读了
分析一下CopyOnWriteArrayList部分源码
1.add(E e)方法,向容器中添加元素

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

在往容器中加元素的过程是加锁的,加锁是通过可重入锁ReentrantLock实现的,如果不加锁的话,多个线程同时添加元素会复制多次。
getArray()获得旧容器中的元素

final Object[] getArray() {
        return array;
    }

Arrays.copyOf(elements, len + 1)进行数组的复制,并返回复制以后新的数组
newElements[len] = e向新的集合中添加元素
setArray(newElements)将旧容器的引用指向新容器

 final void setArray(Object[] a) {
        array = a;
    }

其余向容器中添加元素的方法,比如public void add(int index, E element)实现思路和add(E e)大抵相同
2.get(int index),从容器中获得指定位置的元素

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

实际调用的是get(Object[] a, int index)方法

private E get(Object[] a, int index) {
        return (E) a[index];
    }

得到指定位置的元素就是获得数组中指定位置的元素
从代码中可以看到,对于从容器中读操作是不进行加锁的
3.remove(int 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);
           //移动的距离
            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();
        }
    }

实现的基本思想和add(E e)相同,需要进行加锁,并且会对旧的容器进行复制

4.看一个CopyOnWriteArrayList的构造函数

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

一般我们自己写项目的时候都会选择使用这个构造函数。这个构造函数并没有初始化集合的大小,集合大小为0,但是它没有像ArrayList一样有扩容操作,因为在往这个容器中进行写操作时,实际上并不是往当前容器添加元素,而是会创建出一个比当前容器大1的容器,在对往这个容器中添加元素。所以不需要进行扩容。或者可以这么理解,它在每次添加元素的操作时都进行了一次扩容,每次扩容一个元素的大小

5.CopyOnWriteArrayList的缺点:
最明显的一个致命缺点就是占大量的内存,在往容器中删除元素和添加元素的时候都会在创建一个新的数组,如果垃圾收集器回收不及时的话,并且有很多线程进行写操作,可能会撑爆内存吧。
在一篇博客上看到CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。不是特别理解这一点,volatile Object[] array,array是有volatile修饰的,其保证了内存的可见性,当一个线程对一个共享变量的写操作时,写完立刻就会对其他线程立即可见,那只要写完,其他线程就能读到新添加的值。自己写了个demo进行测试,感觉延迟效果不是很明显
测试类:TestCopyOnWriteArrayList.java

public class TestCopyOnWriteArrayList {
    private static CopyOnWriteArrayList<String> c = new CopyOnWriteArrayList<>();
    private static long startTime;
    private static long endTime;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                c.add("a");
                startTime = System.currentTimeMillis();  //获得添加a以后的时间
                System.out.println("添加了a");
                //添加了a以后让其睡眠,让其他线程有时间执行
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.add("b");
                System.out.println("添加了b");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.add("c");
                System.out.println("添加了c");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.add("d");
                System.out.println("添加了d");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
//                System.out.println("读取第一个元素");
                String s = c.get(0);
                endTime = System.currentTimeMillis();
                System.out.println("读取到a花费时间:" + (endTime - startTime) + "毫秒");
                System.out.println("s: " + s);
            }
        }).start();
    }
}

运行结果:

添加了a
读取到a花费时间:1毫秒
s: a
添加了b
添加了c
添加了d

1毫秒的延迟也不是特别长吧
不知道是不是自己的例子不正确
6.CopyOnWriteArrayList的应用场景:读多写少的场景

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容