点击上方“码农突围”,马上关注

这里是码农充电第一站,回复“666”,获取一份专属大礼包

真爱,请设置“星标”或点个“在看”

作者:myseries

来源:cnblogs.com/myseries/p/10877420.html

写入时复制(CopyOnWrite)思想

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

CopyOnWriteArrayList的实现原理

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

/*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return <tt>true</tt> (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();}}

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

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容器,并且在不同的应用场景中使用。

几个要点

  • 实现了List接口

  • 内部持有一个ReentrantLock lock = new ReentrantLock();

  • 底层是用volatile transient声明的数组 array

  • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

「注:」

volatile (挥发物、易变的):变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变 化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

增删改查

1)增

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();}
}public void add(int index, E element) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;if (index > len || index < 0)throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len);Object[] newElements;int numMoved = len - index;if (numMoved == 0)newElements = Arrays.copyOf(elements, len + 1);else {newElements = new Object[len + 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index, newElements, index + 1,numMoved);}newElements[index] = element;setArray(newElements);} finally {lock.unlock();}
}

2)删

public E remove(int index) {final ReentrantLock lock = this.lock;//获得锁lock.lock();try {Object[] elements = getArray();int len = elements.length;E oldValue = get(elements, index);int numMoved = len - index - 1;if (numMoved == 0)//如果删除的元素是最后一个,直接复制该元素前的所有元素到新的数组setArray(Arrays.copyOf(elements, len - 1));else {//创建新的数组Object[] newElements = new Object[len - 1];//将index+1至最后一个元素向前移动一格System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);setArray(newElements);}return oldValue;} finally {lock.unlock();}
}

3)改

public E set(int index, E element) {final ReentrantLock lock = this.lock;//获得锁lock.lock();try {Object[] elements = getArray();E oldValue = get(elements, index);if (oldValue != element) {int len = elements.length;//创建新数组Object[] newElements = Arrays.copyOf(elements, len);//替换元素newElements[index] = element;//将新数组指向原来的引用setArray(newElements);} else {// Not quite a no-op; ensures volatile write semanticssetArray(elements);}return oldValue;} finally {//释放锁lock.unlock();}
}

4)查

//直接获取index对应的元素
public E get(int index) {return get(getArray(), index);}
private E get(Object[] a, int index) {return (E) a[index];}

CopyOnWrite的应用场景

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

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为什么并发安全且性能比Vector好

我知道Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

最近有有不少老铁在后台留言说,想进大厂,但是算法不好。最近我整理了一份刷题实录,这份刷题实录,也让我进了心仪的大厂。现在开放分享给大家。希望对大家有所帮助。任何的算法题,如同写作文一样,都有一些模板可以套用的。比如面试常考的DP(动态规划),难的是一些关键点是否能想清楚。比如你能写出动态转移方程,这题基本上就可以AC了。
整个刷题实录内容,包括 双子针、动态规划、二分查找、贪心算法、深度优先搜索、字符串、递归、字典树、排序、链表等相关专题内容。图文并茂,附有刷题答案源码。刷题任务的题目,是根据题目的类型来汇总的,总结了八个类别,每个类别下面也总结了5个左右的题型,帮助大家分门别类的突破,所以刷起来相对会更有重点和针对性。如果从头到尾的刷,每周按顺序刷42题,很容易让自己坚持不下来,也会觉得很枯燥。所以在制定计划的时候可以让这个计划变得更“有趣"和针对性,让它看起来更容易实现一点,才会更容易坚持。目前上述内容已打包成完整电子书,具体获取方式如下:扫描关注 Github爱好者社区 公众号;在 Github爱好者社区 公众号后台回复关键词「9999」获取下载地址。扫描关注,回复"9999"即可下载
最近热文•  全球最大的色情网站,保留着西方媒体最后的良心•  卸载 Navicat!事实已证明,正版客户端,它更牛逼……•  太火了!MyBatis Plus 为啥这么牛?•  代码对比工具,我就用这6个???? 长按二维码 2 秒
在公众号后台回复「Java」
获取最新整理的Java学习资料明天见(。・ω・。)

面试官:换人!赶快换人!连CopyOnWriteArrayList都没听过!确实没听过相关推荐

  1. 面试官:这么简单的正则表达式都不会?

    正则表达式有多神奇 最近发现很多的面试和笔试中都经常出现正则表达式相关的题目,面试官:这么简单的正则表达式都不会?面试者:嘴角微微上扬,回以一个不失礼而尴尬的笑 熟练掌握正则表达式,不仅能够在面试中体 ...

  2. hashmap存多少条数据_干货 | 面试官想问的HashMap,都在这一篇里面了!

    来源公众号:非科班的科班 本文思维导图 HashMap简介 HashMap 是很常用的一种集合框架,其底层实现方式在 JDK 1.7和 JDK 1.8中却有很大区别.HashMap 是用来存储数据的, ...

  3. rabbitmq 拉取消息太慢_面试官:消息队列这些我都要问

    作者:mousycoder segmentfault.com/a/1190000021054802 消息队列连环炮 项目里怎么样使用 MQ 的? 为什么要使用消息队列? 消息队列有什么优点和缺点? k ...

  4. 面试官:连Spring三级缓存都答不好,自己走还是我送你?

    面试官:简历上写了精通Spring,那你回答一下Spring为什么用"三级缓存"去解决循环依赖? 我:.......应该有三个缓存的map结构 面试官:具体回答一下 我:平时没认真 ...

  5. 《变态面试官》系列—Java基础你都不会吧!

    我爱学习,学习使我妈快乐,我妈快乐全家快乐 好看请点赞,不喜请轻喷 锲子 一入编程深似海,编程世界Very深. 这里是九神说编程,今天给大家说的是一个顶级大佬闲的无聊,在编程世界已经999级,闲来无事 ...

  6. 阿里面试官:Redis分片集群都不懂?还来面试?

    面试官:聊下Redis的分片集群,先聊 Redis Cluster好咯? 面试官:Redis Cluser是Redis 3.x才有的官方集群方案,这块你了解多少? 候选者:嗯,要不还是从基础讲起呗? ...

  7. 面试官:说说new操作符具体都干了什么?

    一.是什么 在JavaScript中,new操作符用于创建一个给定构造函数的实例对象 例子 function Person(name, age){this.name = name;this.age = ...

  8. 面试官 | 这位连单点登录都不知道,让他回家等通知去吧

    Hi!我是小小,本期又见面了,小小目前正在往回赶,预计14日左右就能到达北京,重新开始自己的实习生涯.我是小小,我们本期的内容是面试常见问题之-单点登录的三种实现方式. 前言 这里主要阐述单点登录的机 ...

  9. 互联网校招 | 大厂面试官常问的java八股文都在了

    前言 作为Java程序员,应该学习怎样的技术?去招聘网站上随便一搜,看看岗位要求就能够十分清楚了,自己具备怎样的技术和能力,就会影响自己的工作环境和社会地位.所以,多学一些大厂技术是百里无一害的! 今 ...

  10. 【运维面试】面试官: 你每天在公司都做啥?

    文章目录 前言 运维碎碎念 7年经验运维 5年工作经验运维 3年工作经验 开发和其他岗位严重的运维 如何回答这个问题 总结 前言 不同公司的运维,负责的内容不一样,有时候很难有统一的答案,我在架构师大 ...

最新文章

  1. StoreFront 2.5让XenDesktop和XenApp如虎添翼
  2. C语言二月天数计算,关于计算两个日期间天数的代码,大家来看看
  3. mybatis3中@SelectProvider的使用技巧
  4. 升级版的数据透视表!用一工具,做出了HR羡慕的人力数据分析
  5. (转)Android权限列表permission说明 (一)
  6. 用Python玩转微信(一)
  7. 7.docker pull
  8. 树莓派自带wifi工作不正常
  9. 调试html代码的快捷键,chrome网页F12调试快捷键
  10. hdu 2037 今年暑假不AC 贪心)
  11. 由2003年的一篇讲座笔记
  12. 多视图信息瓶颈表征学习
  13. c++ 崩溃 正则表达式regex_C++正则表达式regex初探及踩的坑
  14. 成绩忽好忽坏的孩子,往往缺乏良好的学习习惯--一语中的
  15. Scala学习笔记(黑马视频)
  16. 数据隐藏技术揭秘笔记
  17. 整顿一年再次增资近50%,为什么蚂蚁集团要重启IPO?
  18. arcgis地图怎么标注当前位置
  19. 图像复原 matlab代码,图像复原的Matlab实现(源代码)
  20. 获取当前时间时间戳的几种方式

热门文章

  1. Go语言:面向对象编程
  2. EasyUI:中datagrid数据表格的删除、编辑、保存、撤销功能实现
  3. mysql timestamp 值_mysql中TIMESTAMP设置默认值的灵活运用
  4. 斯坦福大学深度学习公开课cs231n学习笔记(2)线性分类器及最优化
  5. Bilateral Filters(双边滤波算法)原理及实现
  6. 贝叶斯球(Bayes ball)
  7. AI技术如何做工程?
  8. LeetCode之二分查找
  9. 神经网络模型参数的压缩
  10. 20190415 - iOS11 无法连接到 App Store 的解决办法