并发容器之CopyOnWriteArrayList

前言

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

什么是CopyOnWrite容器

从字面意思上看是写时复制的容器。通俗理解就是我们往一个容器中添加元素时,不直接往容器中添加,而是先将容器copy,复制出一个新的容器,然后新的容器里添加元素,添加完新的元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,但不需要加锁,因为当前在读的容器中不会添加新的元素,运用一种读写分离容器的思想。

copyOnWriteArrayList实现原理

来看几个方法
(1)add (E e)
这里是要加锁的,因为在add的时候是要Arrays.copyOf出一个容器副本的,如果多线程访问会造成copy多个容器副本出来。
可以看到是在copy完成添加元素之后去将引用指向这个新的数组。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    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();
        }
    }

(2)get(int index)
读的时候并没有加锁,如果读的时候有线程同时去添加元素,还是会读到之前的旧的容器,所以并不用加速,但要求对读的数据一致性没那么高。


    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

应用场景

  • copyOnWrite并发容器用于读多写少的并发场景。比如白名单、黑名单、商品类目等。比如有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单中,黑名单每晚会更新一次。当用户搜索时,会检查关键字在不在黑名单中,如果在,则提示不能搜索。
  • 另外迭代操作远远大于修改操作时,才应该使用“写入时复制”容器。这个准则很好的描述了许多事件通知系统:在分发通知时需要迭代已注册监听器链表,并调用每个监听器,在大多数情况下,注册和注销事件监听器的操作远少于接受事件通知的操作。
  • copyOnWriteArrayList用于替代同步list,在某些情况下它提供了更好的并发性能,并且能在迭代期间不需要对容器进行加锁或者复制。

实现一个copyOnWriteMap容器

  • 简单根据“写入时复制”的思想实现一个map容器,并做简单测试
package collection;

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 简单根据CopyOnWrite容器的思想去实现一个map 只实现了get put putAll方法 且一些临界异常条件没有去处理
 *
 * @author 夸克
 * @create 2018/7/8 15:58
 */
public class CopyOnWriteMap<K, V> extends AbstractMap<K, V> implements Cloneable {

    private volatile Map<K, V> internalMap;

    public CopyOnWriteMap() {
        internalMap = new HashMap<>();
    }

    /**
     * 此方法未实现
     */
    @Override
    public Set<Entry<K, V>> entrySet() {
        return null;
    }

    @Override
    public V get(Object key) {
        // 读时不加锁
        return internalMap.get(key);
    }

    @Override
    public V put(K key, V value) {
        // 写时复制加锁
        synchronized (this) {
            Map<K, V> newMap = new HashMap<K, V>(internalMap);
            V val = newMap.put(key, value);
            internalMap = newMap;
            return val;
        }
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> data) {
        synchronized (this) {
            Map<K, V> newMap = new HashMap<>(internalMap);
            newMap.putAll(data);
            internalMap = newMap;
        }
    }


    public static void main(String[] args) {
        CopyOnWriteMap<Integer, Integer> copyOnWriteMap = new CopyOnWriteMap();
        // 初始化数据
        Map<Integer, Integer> map = new HashMap();
        for (int i = 0; i < 10; i++) {
            map.put(i, i);
        }

        // 读五次线程
        for (int i = 0; i < 5; i++) {
            Thread read = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 方便写线程写入数据 如果不加sleep 是读不到数据的,因为是在新复制的容器中写。 
                        // 测试copyOnWrite思想
                        Thread.sleep(500);
                    } catch (InterruptedException e) {

                    }
                    System.out.println(copyOnWriteMap.get(5));
                }
            });
            read.start();
        }

        // 写线程
        Thread write = new Thread(new Runnable() {

            @Override
            public void run() {
                map.forEach((k, v) -> {
                    copyOnWriteMap.put(k, v);
                });
            }
        });
        write.start();

    }

}

代码地址

https://github.com/zhanglijun1217/juc

缺点

  • 显然,每次修改容器的时候都会复制底层数组,这回造成一定的内存开销,特别是当容器的规模很大的时候,可能有将内存撑爆的可能性存在。这时候可能要考虑别的容器。另外上边也提到了这种复制一份新的容器延迟的做法会有数据一致性的问题,如果你对写入的数据读出来实时性很高,那么久不要去选择copyOnWrite容器。

其他文章

引用说明

1.https://www.cnblogs.com/dolphin0520/p/3938914.html
2.《java并发编程实战》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值