详见: 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》,后来,因为有很多线程安全上的事,就被去掉了。
相关推荐
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官方发布的针对Java的最新版本连接器,版本号为8.0.11。 1. **JDBC接口**:JDBC是Java语言访问数据库的标准接口,由Java SE的java.sql包提供。它定义了...
在计算机编程和操作系统领域,Copy-on-Write(COW)是一种优化策略,主要用于内存管理、文件系统以及多线程环境中的数据共享。该机制的核心思想是:当多个进程或线程试图修改同一个数据时,只有在实际修改数据时才...
这份"java-java面试题库整理-基础-JVM-线程并发-框架等.zip"文件提供了一个全面的复习资源,帮助求职者准备Java相关的面试。 1. **Java基础知识** - 类与对象:Java是一种面向对象的语言,了解类的定义、构造器、...
Java 多线程与并发是 Java 编程语言中的一种机制,用于提高程序的执行效率和响应速度。多线程的出现是为了解决 CPU、内存、I/O 设备速度差异的问题,通过分时复用 CPU、缓存和进程、线程机制来平衡速度差异。 Java...
而FastDFS-client-java则是FastDFS系统中的Java客户端,它为Java应用程序提供了与FastDFS服务器进行交互的能力。本文将详细介绍FastDFS-client-java 1.26版本的主要特性和使用方法。 一、FastDFS-client-java 1.26...
1. JDBC(Java Database Connectivity)API:JDBC是Java平台中用于与各种数据库进行交互的一组接口和类,由Sun Microsystems开发并定义,现已成为Java标准的一部分。它为Java程序员提供了统一的数据库访问方式,屏蔽...
MySQL Connector/J 5.1.37 是 MySQL 数据库与 Java 应用程序之间的关键桥梁,它是 MySQL 官方提供的一个驱动程序,使得 Java 开发人员能够通过 JDBC(Java Database Connectivity)接口与 MySQL 数据库进行交互。...
这份名为"面试真题包含spring-java-集合-框架-并发-spring-运维-数据库等多领域45卷合集.rar"的压缩包是为准备Java相关面试的求职者精心整理的资源库。它包含了45套涵盖多个领域的面试题,旨在帮助求职者全面复习和...
Java并发编程是软件开发中的重要领域,特别是在大型系统和高并发场景中不可或缺。"13-Java并发编程学习宝典.zip" 包含了一系列关于Java并发编程的学习资源,旨在帮助开发者掌握多线程编程的核心技术和最佳实践。以下...
在本课程"计算机后端-Java-Java高并发从入门到面试教程-课程准备"中,我们将深入探讨Java编程语言在处理高并发场景下的核心概念和技术。Java是企业级应用开发的重要选择,尤其是在大型分布式系统中,其强大的并发...
2. CopyOnWriteArrayList 和 CopyOnWriteArraySet 是基于写时复制(Copy-On-Write)策略的容器,适用于读多写少的情况。写操作时,会创建一个新的副本并在新副本上进行修改,然后替换原有引用。这使得读操作无需加锁...
Java并发编程是Java开发中的重要领域,特别是在大型的后端服务开发中,处理高并发请求是必不可少的技能。本教程“计算机后端-Java-Java高并发从入门到面试教程-并发基础.zip”旨在帮助开发者从零开始学习并掌握Java...
其中,`CopyOnWriteArrayList`和`CopyOnWriteArraySet`是典型的写时复制(Copy-On-Write,COW)容器,它们在写操作时创建容器副本,确保读操作不会被阻塞,从而提高并发性能。当线程尝试修改容器时,它会先复制一份...
在本课程中,我们深入探讨了Java高并发编程这一核心领域,这不仅是Java开发者必备的技能,也是在面试中常被考察的知识点。这个“计算机后端-Java-Java高并发从入门到面试教程”旨在帮助初学者和有经验的开发者们掌握...
计算机后端-Java-Java高并发从入门到面试教程-息队列思路.zip
Java高并发编程是Java开发中的核心技能之一,尤其在大型互联网应用中不可或缺。这个课程资料主要涵盖了从基础知识到面试必备的Java并发知识体系。以下是对这些主题的详细讲解: 1. **并发基础** - **线程与进程**...
1. **JDBC驱动**: JDBC是Java中的一组接口和类,定义了Java应用程序如何与各种数据库通信的标准。`mysql-connector-java`是MySQL公司提供的JDBC驱动实现,它实现了JDBC API,使得Java程序可以无缝连接到MySQL服务器...