美文网首页
java集合类-6-CopyOnWrite

java集合类-6-CopyOnWrite

作者: 宠辱不惊的咸鱼 | 来源:发表于2019-09-30 09:18 被阅读0次

    CopyOnWriteArrayList

    概述

    • JDK 1.5出现
    • 写时复制容器
    • 添加元素时,不直接往当前容器添加
    • 将当前容器进行Copy,往新容器添加元素
    • 添加完,将原容器引用指向新容器
    • 好处
      • 读写分离

    机制

    • 添加时需加锁,否则多线程写时会Copy出N个副本
    • 读时无需加锁,若读时有线程正在向CopyOnWriteArrayList添加数据,会读到旧数据,因为写不会锁住旧的

    添加

    private transient volatile Object[] array;
    
    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();
        }
    }
    
      • 加锁
      • 复制(可以看到效率是蛮低的)
      • 加入Element
      • 重定向数组引用
      • 释放锁

    批量添加

    public boolean addAll(Collection<? extends E> c) {
        Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ? ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
        if (cs.length == 0)
            return false;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len == 0 && cs.getClass() == Object[].class)
                setArray(cs);
            else {
                Object[] newElements = Arrays.copyOf(elements, len + cs.length);
                System.arraycopy(cs, 0, newElements, len, cs.length);
                setArray(newElements);
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
    

    场景

    • 读多写少
    • 比如白名单,黑名单,商品类目的访问和更新场景。假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索;这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次;当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索

    缺点

    • 内存占用问题
      • 写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)
      • 如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC
      • 之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长
      • 针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制;或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap
    • 数据一致性问题
      • 只能保证数据的最终一致性,不能实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器

    性能测试

    • CopyOnWriteArrayList在线程对其进行变更操作的时候,会拷贝一个新的数组以存放新的字段,因此写操作性能很差
    • Collections.synchronizedList读操作采用了synchronized,因此读性能较差
    public class App {
        private static List<String> arrayList = Collections.synchronizedList(new ArrayList<String>());
        private static List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();
        private static CountDownLatch cdl1 = new CountDownLatch(2);
        private static CountDownLatch cdl2 = new CountDownLatch(2);
        private static CountDownLatch cdl3 = new CountDownLatch(2);
        private static CountDownLatch cdl4 = new CountDownLatch(2);
    
        static class Thread1 extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++)
                    arrayList.add(String.valueOf(i));
                cdl1.countDown();
            }
        }
    
        static class Thread2 extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++)
                    copyOnWriteArrayList.add(String.valueOf(i));
                cdl2.countDown();
            }
        }
    
        static class Thread3 extends Thread1 {
            @Override
            public void run() {
                int size = arrayList.size();
                for (int i = 0; i < size; i++)
                    arrayList.get(i);
                cdl3.countDown();
            }
        }
    
        static class Thread4 extends Thread1 {
            @Override
            public void run() {
                int size = copyOnWriteArrayList.size();
                for (int i = 0; i < size; i++)
                    copyOnWriteArrayList.get(i);
                cdl4.countDown();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            long start1 = System.currentTimeMillis();
            new Thread1().start();
            new Thread1().start();
            cdl1.await();
            System.out.println("arrayList add: " + (System.currentTimeMillis() - start1));
    
            long start2 = System.currentTimeMillis();
            new Thread2().start();
            new Thread2().start();
            cdl2.await();
            System.out.println("copyOnWriteArrayList add: " + (System.currentTimeMillis() - start2));
    
            long start3 = System.currentTimeMillis();
            new Thread3().start();
            new Thread3().start();
            cdl3.await();
            System.out.println("arrayList get: " + (System.currentTimeMillis() - start3));
    
            long start4 = System.currentTimeMillis();
            new Thread4().start();
            new Thread4().start();
            cdl4.await();
            System.out.println("copyOnWriteArrayList get: " + (System.currentTimeMillis() - start4));
        }
    }
    
    map-002.jpg

    CopyOnWriteArraySet

    相关文章

      网友评论

          本文标题:java集合类-6-CopyOnWrite

          本文链接:https://www.haomeiwen.com/subject/vedcpctx.html