`

聊聊并发-Java中的Copy-On-Write容器

阅读更多

详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp78

 

聊聊并发-Java中的Copy-On-Write容器

 

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

什么是CopyOnWrite容器CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

 

CopyOnWriteArrayList的实现原理在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。以下代码是向ArrayList里添加元素,可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。

01public boolean add(T e) {

02    final ReentrantLock lock = this.lock;

03    lock.lock();

04    try {

05 

06        Object[] elements = getArray();

07 

08        int len = elements.length;

09        // 复制出新数组

10 

11        Object[] newElements = Arrays.copyOf(elements, len + 1);

12        // 把新元素添加到新数组里

13 

14        newElements[len] = e;

15        // 把原数组引用指向新数组

16 

17        setArray(newElements);

18 

19        return true;

20 

21    } finally {

22 

23        lock.unlock();

24 

25    }

26 

27}

28 

29final void setArray(Object[] a) {

30    array = a;

31}

读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayList。

1public E get(int index) {

2    return get(getArray(), index);

3}

JDK中并没有提供CopyOnWriteMap,我们可以参考CopyOnWriteArrayList来实现一个,基本代码如下:

01import java.util.Collection;

02import java.util.Map;

03import java.util.Set;

04 

05public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {

06    private volatile Map<K, V> internalMap;

07 

08    public CopyOnWriteMap() {

09        internalMap = new HashMap<K, V>();

10    }

11 

12    public V put(K key, V value) {

13 

14        synchronized (this) {

15            Map<K, V> newMap = new HashMap<K, V>(internalMap);

16            V val = newMap.put(key, value);

17            internalMap = newMap;

18            return val;

19        }

20    }

21 

22    public V get(Object key) {

23        return internalMap.get(key);

24    }

25 

26    public void putAll(Map<? extends K, ? extends V> newData) {

27        synchronized (this) {

28            Map<K, V> newMap = new HashMap<K, V>(internalMap);

29            newMap.putAll(newData);

30            internalMap = newMap;

31        }

32    }

33}

实现很简单,只要了解了CopyOnWrite机制,我们可以实现各种CopyOnWrite容器,并且在不同的应用场景中使用。

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

01package com.ifeve.book;

02 

03import java.util.Map;

04 

05import com.ifeve.book.forkjoin.CopyOnWriteMap;

06 

07/**

08 * 黑名单服务

09 *

10 * @author fangtengfei

11 *

12 */

13public class BlackListServiceImpl {

14 

15    private static CopyOnWriteMap<String, Boolean> blackListMap = newCopyOnWriteMap<String, Boolean>(

16            1000);

17 

18    public static boolean isBlackList(String id) {

19        return blackListMap.get(id) == null ? false : true;

20    }

21 

22    public static void addBlackList(String id) {

23        blackListMap.put(id, Boolean.TRUE);

24    }

25 

26    /**

27     * 批量添加黑名单

28     *

29     * @param ids

30     */

31    public static void addBlackList(Map<String,Boolean> ids) {

32        blackListMap.putAll(ids);

33    }

34 

35}

代码很简单,但是使用CopyOnWriteMap需要注意两件事情:

1. 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。

2. 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。

CopyOnWrite的缺点CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。

内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两份对象的内存。如果这个对象占用的内存比较大,比如说200M左右,那么这个时候很有可能造成频繁的GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用RT(响应时间)也随之变长。

针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器。

数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

关于C++的STL中,曾经也有过Copy-On-Write的玩法,参见陈皓的《C++ STL String类中的Copy-On-Write》,后来,因为有很多线程安全上的事,就被去掉了。

 

转自:http://ifeve.com/java-copy-on-write/

分享到:
评论

相关推荐

    62-Java并发编程实战

    62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java...

    mysql-connector-java-8.0.11

    在这个"mysql-connector-java-8.0.11"压缩包中,包含的是MySQL官方发布的针对Java的最新版本连接器,版本号为8.0.11。 1. **JDBC接口**:JDBC是Java语言访问数据库的标准接口,由Java SE的java.sql包提供。它定义了...

    Copy-on-write, 写时复制

    在计算机编程和操作系统领域,Copy-on-Write(COW)是一种优化策略,主要用于内存管理、文件系统以及多线程环境中的数据共享。该机制的核心思想是:当多个进程或线程试图修改同一个数据时,只有在实际修改数据时才...

    java-java面试题库整理-基础-JVM-线程并发-框架等.zip

    这份"java-java面试题库整理-基础-JVM-线程并发-框架等.zip"文件提供了一个全面的复习资源,帮助求职者准备Java相关的面试。 1. **Java基础知识** - 类与对象:Java是一种面向对象的语言,了解类的定义、构造器、...

    Java 多线程与并发(1-26)-Java 并发 - 理论基础.pdf

    Java 多线程与并发是 Java 编程语言中的一种机制,用于提高程序的执行效率和响应速度。多线程的出现是为了解决 CPU、内存、I/O 设备速度差异的问题,通过分时复用 CPU、缓存和进程、线程机制来平衡速度差异。 Java...

    fastdfs-client-java1.26

    而FastDFS-client-java则是FastDFS系统中的Java客户端,它为Java应用程序提供了与FastDFS服务器进行交互的能力。本文将详细介绍FastDFS-client-java 1.26版本的主要特性和使用方法。 一、FastDFS-client-java 1.26...

    mysql驱动包 mysql-connector-java-5.1.7-bin.jar

    1. JDBC(Java Database Connectivity)API:JDBC是Java平台中用于与各种数据库进行交互的一组接口和类,由Sun Microsystems开发并定义,现已成为Java标准的一部分。它为Java程序员提供了统一的数据库访问方式,屏蔽...

    mysql-connector-java-5.1.37-bin.jar

    MySQL Connector/J 5.1.37 是 MySQL 数据库与 Java 应用程序之间的关键桥梁,它是 MySQL 官方提供的一个驱动程序,使得 Java 开发人员能够通过 JDBC(Java Database Connectivity)接口与 MySQL 数据库进行交互。...

    面试真题包含spring-java-集合-框架-并发-spring-运维-数据库等多领域45卷合集.rar

    这份名为"面试真题包含spring-java-集合-框架-并发-spring-运维-数据库等多领域45卷合集.rar"的压缩包是为准备Java相关面试的求职者精心整理的资源库。它包含了45套涵盖多个领域的面试题,旨在帮助求职者全面复习和...

    13-Java并发编程学习宝典.zip

    Java并发编程是软件开发中的重要领域,特别是在大型系统和高并发场景中不可或缺。"13-Java并发编程学习宝典.zip" 包含了一系列关于Java并发编程的学习资源,旨在帮助开发者掌握多线程编程的核心技术和最佳实践。以下...

    计算机后端-Java-Java高并发从入门到面试教程-课程准备.zip

    在本课程"计算机后端-Java-Java高并发从入门到面试教程-课程准备"中,我们将深入探讨Java编程语言在处理高并发场景下的核心概念和技术。Java是企业级应用开发的重要选择,尤其是在大型分布式系统中,其强大的并发...

    并发容器的原理,7大并发容器详解、及使用场景

    2. CopyOnWriteArrayList 和 CopyOnWriteArraySet 是基于写时复制(Copy-On-Write)策略的容器,适用于读多写少的情况。写操作时,会创建一个新的副本并在新副本上进行修改,然后替换原有引用。这使得读操作无需加锁...

    计算机后端-Java-Java高并发从入门到面试教程-并发基础.zip

    Java并发编程是Java开发中的重要领域,特别是在大型的后端服务开发中,处理高并发请求是必不可少的技能。本教程“计算机后端-Java-Java高并发从入门到面试教程-并发基础.zip”旨在帮助开发者从零开始学习并掌握Java...

    Java从同步容器到并发容器的操作过程

    其中,`CopyOnWriteArrayList`和`CopyOnWriteArraySet`是典型的写时复制(Copy-On-Write,COW)容器,它们在写操作时创建容器副本,确保读操作不会被阻塞,从而提高并发性能。当线程尝试修改容器时,它会先复制一份...

    计算机后端-Java-Java高并发从入门到面试教程-课程总结.zip

    在本课程中,我们深入探讨了Java高并发编程这一核心领域,这不仅是Java开发者必备的技能,也是在面试中常被考察的知识点。这个“计算机后端-Java-Java高并发从入门到面试教程”旨在帮助初学者和有经验的开发者们掌握...

    计算机后端-Java-Java高并发从入门到面试教程-息队列思路.zip

    计算机后端-Java-Java高并发从入门到面试教程-息队列思路.zip

    计算机后端-Java-Java高并发从入门到面试教程-发课程资料.zip

    Java高并发编程是Java开发中的核心技能之一,尤其在大型互联网应用中不可或缺。这个课程资料主要涵盖了从基础知识到面试必备的Java并发知识体系。以下是对这些主题的详细讲解: 1. **并发基础** - **线程与进程**...

    mysql驱动包mysql-connector-java-5.5.19.jar

    1. **JDBC驱动**: JDBC是Java中的一组接口和类,定义了Java应用程序如何与各种数据库通信的标准。`mysql-connector-java`是MySQL公司提供的JDBC驱动实现,它实现了JDBC API,使得Java程序可以无缝连接到MySQL服务器...

Global site tag (gtag.js) - Google Analytics