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

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

 

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

什么是CopyOnWrite容器

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

public boolean add(T 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();}}final void setArray(Object[] a) {array = a;
}

CopyOnWriteArrayList的实现原理

在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。以下代码是向ArrayList里添加元素,可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。读的时候不需要加锁,如果读的时候有多个线程正在向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容器。

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327564.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android 枚举类型比较大小写,Spring 3.0 MVC绑定枚举区分大小写

如果我有一个像这样的Spring控制器的RequestMapping ...RequestMapping(method RequestMethod.GET, value "{product}")public ModelAndView getPage(PathVariable Product product)产品是一个枚举。例如。 Product.Home当我请求该页面时&#xff0c;mysite.com/ho…

集成SpringSecurity---SpringBoot

集成SpringSecurity 安全简介 在 Web 开发中&#xff0c;安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求&#xff0c;但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题&#xff0c;就可能陷入一个两难的境地&#xff1a;一方面&am…

OkHttp上传Json嵌套对象

public static DevInfoVo queryRCP() throws Exception {// 东八区时区Calendar cal Calendar.getInstance(TimeZone.getTimeZone("GMT8:00"));//JSONObject obj JSON.parseObject(JSON.toJSONString(pojo));JSONObject obj new JSONObject();obj.put("Autho…

大新闻!Magic Leap造假,HoloLens即将入华商用

昨天微软搞了大新闻&#xff0c;Terry和Alexi到了深圳&#xff0c;在WinHEC大会上宣布了2017上半年HoloLens正式入华商用。 而唯一竞争对手Magic Leap今天也被曝光其设备造假&#xff0c;各大科技媒体纷纷报道&#xff0c;部分相关报道如下&#xff1a; 【新浪科技】Magic Lea…

struts+hibernate+oracle+easyui实现lazyout组件的简单案例——struts.xml配置详情

最后我们来看一下struts.xml里面是怎么配置的呢&#xff0c; struts.xml: <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://strut…

集合框架 Queue---BlockingQueue详解

转载自 集合框架 Queue---BlockingQueue详解 摘要&#xff1a;本例介绍一个特殊的队列:BlockingQueue,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒,同样,如果BlockingQueue是满的,任何试图往里存东西的操…

cursor 过滤 android,Android cursor query方法详解

1.cursor query 方法入参public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, String sortOrder)第一个参数&#xff0c;uri&#xff0c;rui是什么呢&#xff1f;好吧&#xff0c;上面我们提到了Android提供内容的叫Provider&a…

java实现动态验证码源代码——jsp页面

我们通常在其他网站注册或者登陆的时候会发现一个很高大上的验证码&#xff0c;那么怎么用java代码实现呢&#xff1f;下面我给大家贴一下我写的方法&#xff1a; 此文章是写的前台jsp页面的代码&#xff1a; <% page language"java" import"java.util.*&quo…

集成Swagger(API)---SpringBoot

集成Swagger(API) 学习目标: 了解Swagger的概念及作用掌握在项目中集成Swagger自动生成API文档Swagger简介 前后端分离 前端 -> 前端控制层、视图层后端 -> 后端控制层、服务层、数据访问层前后端通过API进行交互前后端相对独立且松耦合产生的问题 前后端集成,前端或者…

win10硬盘修复工具使用教程

DiskGenius 下载地址https://www.diskgenius.cn/download.php https://baijiahao.baidu.com/s?id1651410139210648102&wfrspider&forpc win10硬盘修复工具使用教程 小白一键重装系统 发布时间&#xff1a;19-11-2810:17惠州市早点科技官方帐号&#xff0c;优质创作者…

.NET Core Tools转向使用MSBuild项目格式

微软之前为了让.NET Core和ASP.NET Core能够支持Windows Visual Studio之外的开发平台&#xff0c;创建了基于project.json格式的项目系统。不过可惜&#xff0c;这种格式与之前的.csproj/MSBuild无法兼容。来自微软的Rich Lander近期宣布&#xff0c;.NET Core Tools将在最新的…

java多线程之:SynchronousQueue队列

转载自 java多线程之&#xff1a;SynchronousQueue队列 SynchronousQueue是这样一种阻塞队列&#xff0c;其中每个 put 必须等待一个 take&#xff0c;反之亦然。同步队列没有任何内部容量&#xff0c;甚至连一个队列的容量都没有。 不能在同步队列上进行 peek&#xff…

android:background大小,小Demo小知识-android:foreground与android:background

-----------------------------------------------前言君--------------------------------------------------正好碰到了这个foreground属性平时没怎么用到过。这次用到&#xff0c;就特意的去看了下。在这里记录一下。------------------------------------------------正文君…

java实现动态验证码源代码——绘制验证码的jsp

接着上一篇的前台jsp页面&#xff0c;我们单独写一个绘制验证码的jsp页面&#xff1a; <% page contentType"image/jpeg"import"java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*"pageEncoding"utf-8"%> <%!Color getRandColo…

异步任务---SpringBoot

异步任务 同步 1、创建一个service包 2、创建一个类AsyncService 异步处理还是非常常用的&#xff0c;比如我们在网站上发送邮件&#xff0c;后台会去发送邮件&#xff0c;此时前台会造成响应不动&#xff0c;直到邮件发送完毕&#xff0c;响应才会成功&#xff0c;所以我们…

idea报错:Error:java: JDK isn‘t specified for module ‘xxx‘

idea报错&#xff1a;Error:java: JDK isn‘t specified for module ‘xxx‘ Code_LiYi 2020-07-01 11:54:34 274 收藏 分类专栏&#xff1a; 学海漂游 版权 错误&#xff1a;idea报错Error:java:JDK isn’t specified for module “XXXX” 原因&#xff1a;项目中的.idea文…

微软推出《我的世界》“编程一小时”免费教程,携手Code.org普及计算机科学教育

为了继续支持全球计算机科学教育周期间的“编程一小时”年度全球活动&#xff08;12月5日至11日&#xff09;&#xff0c;微软和Code.org共同发布了针对学生和教育工作者的《我的世界》造物主版编程教程。网页版全新教程现已免费上线&#xff0c;初学者可以通过教程中简单的游戏…

集合框架 Queue---ArrayBlockingQueue

转载自 集合框架 Queue---ArrayBlockingQueue 摘要&#xff1a;java.util.concurrent类java.util.concurrent 类 ArrayBlockingQueue<E> java.lang.Object java.util.AbstractCollection<E> java.util.AbstractQueue<E> java.util.…

android 处理http状态码,OkHttp(Retrofit)对于http状态码202的处理

http code 202 :The request has been accepted for processing, but the processing has not been completed.这时候&#xff0c;服务器给你的body是空的&#xff0c;如果你使用去解析为json&#xff0c;那么&#xff0c;恭喜你java.io.EOFException: End of input at line 1 …

java实现动态验证码源代码——接受ajax的jsp

此篇主要介绍的是接受前台ajax的数据&#xff0c;判断输入的验证码是否正确&#xff1a; <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <%//从session里面获取图片验证码String inputCode request.getParameter…