原文链接:

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

  

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

什么是CopyOnWrite容器

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

CopyOnWriteArrayList的实现原理

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

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

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

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

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

import java.util.Collection;
import java.util.Map;
import java.util.Set; public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {
private volatile Map<K, V> internalMap; public CopyOnWriteMap() {
internalMap = new HashMap<K, V>();
} 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;
}
} public V get(Object key) {
return internalMap.get(key);//读方法不需要加锁 读到的是旧数据
} public void putAll(Map<? extends K, ? extends V> newData) {
synchronized (this) {
Map<K, V> newMap = new HashMap<K, V>(internalMap);
newMap.putAll(newData);
internalMap = newMap; //修改完,指向新的容器
}
}
}

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

CopyOnWrite的应用场景

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

package com.ifeve.book;

import java.util.Map;

import com.ifeve.book.forkjoin.CopyOnWriteMap;

/**
* 黑名单服务
*
* @author fangtengfei
*
*/
public class BlackListServiceImpl { private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(
1000); public static boolean isBlackList(String id) {
return blackListMap.get(id) == null ? false : true;
} public static void addBlackList(String id) {
blackListMap.put(id, Boolean.TRUE);
} /**
* 批量添加黑名单
*
* @param ids
*/
public static void addBlackList(Map<String,Boolean> ids) {
blackListMap.putAll(ids);
} }

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

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

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

CopyOnWrite的缺点

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

  内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

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

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

CopyOnWriteArrayList小结:

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

【java并发容器】并发容器之CopyOnWriteArrayList的更多相关文章

  1. Java并发编程:并发容器之CopyOnWriteArrayList(转载)

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  2. Java并发编程:并发容器之CopyOnWriteArrayList

    转载: Java并发编程:并发容器之CopyOnWriteArrayList Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个 ...

  3. 【转】Java并发编程:并发容器之CopyOnWriteArrayList

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

  4. 11、Java并发编程:并发容器之CopyOnWriteArrayList

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  5. java并发编程:并发容器之CopyOnWriteArrayList(转)

    原文:http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开大家都在共享同一个内容,当某个 ...

  6. 【多线程】Java并发编程:并发容器之CopyOnWriteArrayList(转载)

    原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容 ...

  7. (转)Java并发编程:并发容器之CopyOnWriteArrayList

    原文链接:http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容, ...

  8. Java并发编程:并发容器之CopyOnWriteArrayList<转>

    原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容 ...

  9. Java并发--并发容器之CopyOnWriteArrayList

    原文链接:http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容, ...

随机推荐

  1. HTML5中Canvas概述

    一.HTML5 Canvas历史 Canvas的概念最初是由苹果公司提出的,用于在Mac OS X WebKit中创建控制板部件(dashboard widget).在Canvas出现之前,开发人员若 ...

  2. 虚拟机中的CentOS7如何上网?---https://blog.csdn.net/nothing2017/article/details/61420767

    虚拟机中的CentOS7如何上网?https://blog.csdn.net/nothing2017/article/details/61420767

  3. springboot项目--传入参数校验-----SpringBoot开发详解(五)--Controller接收参数以及参数校验----https://blog.csdn.net/qq_31001665/article/details/71075743

    https://blog.csdn.net/qq_31001665/article/details/71075743 springboot项目--传入参数校验-----SpringBoot开发详解(五 ...

  4. noip模拟赛 罪犯分组

    分析:看了题后没别的思路,感觉就是dp,普通dp的话状态和方程实在是不好设计,观察数据,发现N非常小,暗示了这道题要用状压dp来做. 先枚举每个集合,再用O(n^2)的暴力看这个集合内有多少个冲突,如 ...

  5. [bzoj1001]狼爪兔子[平面图的最小割等于其对偶图的最短路]

    一定要仔细算内存,,,又少写一个零.. #include <bits/stdc++.h> using namespace std; template<const int _n,con ...

  6. 【Google Chrome】Google Chrome快捷键大全

    相信很多朋友在使用过Google Chrome之后,就会不想回到原先使用的浏览器了,尤其是IE.没错Google Chrome的优点很多,已经获得了一大部分网友们的用户,软件志现在也是Firefox+ ...

  7. TCP/IP协议1

    1.分层 应用层 telent 远程登录,ftp 文件传输协议,smtp 简单邮件传送协议 snmp 简单网络管理协议 email 运输层  tcp(提供可靠的数据通信)和udp(数据报的分组从一台主 ...

  8. 用Docker创建Nexus

    步骤如下: 1. 创建持久化目录 $ mkdir /some/dir/nexus-data && chown -R 200 /some/dir/nexus-data 2. 创建镜像并运 ...

  9. gogs: 如何恢复repository

    当某天gogs的数据库突然崩溃,配置数据全部消失后,要如何将之前git的repository重新加入到gogs中呢?(别问了,那个倒霉的人就是我) step 1, 2, 3, go... 1. 进入g ...

  10. vijos- P1383盗窃-黑珍珠 (python + 代码优化)

    P1383盗窃-黑珍珠 Accepted 标签:怪盗基德 VS OIBH[显示标签] 背景 怪盗基德 VS OIBH 第二话 描写叙述 今次怪盗基德再次对阵OIBH,目标是Black Star!基德已 ...