CopyOnWrite写入时复制

  CopyOnWrite,即快照模式,写入时复制就是不同线程访问同一资源的时候,会获取相同的指针指向这个资源,只有在写操作,才会去复制一份新的数据,然后新的数据在被写操作完后立马被其他线程看到最新的数据变化,然后之前获取的指针会指向新的数据,但在写操作未结束时,其他线程仍然能访问最初的资源。此做法主要的优点是如果没有线程进行写操作,就不会进行数据副本的复制,因此多个线程只是读取操作时可以共享同一份资源。

  下面以CopyOnWriteArrayList为例:

  测试:

    public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println("主线程-0:"+list.toString());
new Thread(()->{
System.out.println("读子线程-0:"+list.toString());
}).start();
new Thread(()->{
list.add("d");
System.out.println("写子线程-0:"+list.toString());
}).start();
new Thread(()->{
System.out.println("读子线程-1:"+list.toString());
}).start();
list.add("e");
new Thread(()->{
System.out.println("读子线程-2:"+list.toString());
}).start();
System.out.println("主线程-1:"+list.toString());
new Thread(()->{
list.add("f");
System.out.println("写子线程-1:"+list.toString());
}).start();
System.out.println("主线程-2:"+list.toString());
new Thread(()->{
System.out.println("读子线程-3:"+list.toString());
}).start();
}
//=======结果========
主线程-0:[a, b, c]
读子线程-0:[a, b, c]
写子线程-0:[a, b, c, d]
读子线程-1:[a, b, c, d, e]//主线程写e立马被读子线程1发现
主线程-1:[a, b, c, d, e]//主线程写e后输出
读子线程-2:[a, b, c, d, e]
主线程-2:[a, b, c, d, e]
写子线程-1:[a, b, c, d, e, f]
读子线程-3:[a, b, c, d, e, f]

  CopyOnWriteArrayList.add/set/remove/get源码探究

    add:

    private transient volatile Object[] array;//volatile确保数组的可见性
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);//重新拷贝一份新数组,长度+1
newElements[len] = e;//元素加入新数组
setArray(newElements);//数组引用重新指向新数组,即进行旧数组的覆盖
return true;
} finally {
lock.unlock();//释放锁
}
}

  set:

    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 semantics
setArray(elements);//旧元素和新元素一致
}
return oldValue;//返回指定位置的旧元素
} finally {
lock.unlock();
}
}

  remove:

    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];//创建新数组,长度-1
System.arraycopy(elements, 0, newElements, 0, index);//复制指定位置前面的数据
System.arraycopy(elements, index + 1, newElements, index,
numMoved);//复制指定位置后面的数据
setArray(newElements);//数组引用重新指向
}
return oldValue;
} finally {
lock.unlock();
}
}

  get:从中可以看到,没有加锁,直接返回指定位置的元素

    public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}

  CopyOnWriteArrayList探讨:

  1. CopyOnWriteArrayList和Vector的比较:Vector每个方法都加了synchronized,相比CopyOnWriteArrayList只在写操作加锁性能要提升很多;
  2. CopyOnWriteArrayList适合读多写少的并发场景,比如配置、白名单,黑名单,商品类目的访问和更新场景、物流地址等变化非常少的数据;
  3. CopyOnWriteArrayList存在内存问题,即每次的写操作都要进行资源的复制、替换,如果资源对象占用的内存过大,可能导致频繁的Yong GC和Full GC,会造成程序的响应时间变长;
  4. CopyOnWriteArrayList尽量使用批量添加操作addAll方法;
  5. CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

  CopyOnWriteArraySet

  一个Set使用内部CopyOnWriteArrayList其所有操作。

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable { private final CopyOnWriteArrayList<E> al; public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
public boolean add(E e) {
return al.addIfAbsent(e);
}
}
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean addIfAbsent(E e) {//如果元素已经存在,返回false,否则进行写操作(CopyOnWrite)
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
}

多线程高并发编程(9) -- CopyOnWrite写入时复制的更多相关文章

  1. Java 多线程高并发编程 笔记(一)

    本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...

  2. 多线程高并发编程(3) -- ReentrantLock源码分析AQS

    背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...

  3. Java 多线程高并发编程 笔记(二)

    1. 单例模式(在内存之中永远只有一个对象) 1.1 多线程安全单例模式——不使用同步锁 public class Singleton { private static Singleton sin=n ...

  4. 多线程高并发编程(12) -- 阻塞算法实现ArrayBlockingQueue源码分析(1)

    一.前言 前文探究了非阻塞算法的实现ConcurrentLinkedQueue安全队列,也说明了阻塞算法实现的两种方式,使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出 ...

  5. 多线程高并发编程(7) -- Future源码分析

    一.概念 A Future计算的结果. 提供方法来检查计算是否完成,等待其完成,并检索计算结果. 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪. 取消由cancel方法执行 ...

  6. 多线程高并发编程(10) -- ConcurrentHashMap源码分析

    一.背景 前文讲了HashMap的源码分析,从中可以看到下面的问题: HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低 ...

  7. 多线程高并发编程(4) -- ReentrantReadWriteLock读写锁源码分析

    背景: ReentrantReadWriteLock把锁进行了细化,分为了写锁和读锁,即独占锁和共享锁.独占锁即当前所有线程只有一个可以成功获取到锁对资源进行修改操作,共享锁是可以一起对资源信息进行查 ...

  8. 多线程高并发编程(5) -- CountDownLatch、CyclicBarrier源码分析

    一.CountDownLatch 1.概念 public CountDownLatch(int count) {//初始化 if (count < 0) throw new IllegalArg ...

  9. 多线程高并发编程(8) -- Fork/Join源码分析

    一.概念 Fork/Join就是将一个大任务分解(fork)成许多个独立的小任务,然后多线程并行去处理这些小任务,每个小任务处理完得到结果再进行合并(join)得到最终的结果. 流程:任务继承Recu ...

随机推荐

  1. C++获取char值

    直接获取内存地址,不需要定义指针类型的方法,(当然也就不需要释放了)USES_CONVERSION;    if (myFun1)    {        CString _input;       ...

  2. centos 部署 vue项目

    安装Nodejs 下载安装包,可选择其他版本 node-v10.16.0-linux-x64.tar.xz 将下载文件上传至linux服务器并解压 tar -xvf node-v10.16.0-lin ...

  3. CSS选择器与CSS的继承,层叠和特殊性

    什么是选择器?选择器{样式;},在{}之前的部分就是"选择器","选择器"指明了{}中的"样式"的作用对象,也就是"样式" ...

  4. office 365 激活

    将以下代码复制到记事本 @echo off title Activate Microsoft Office ALL versions &echo - Microsoft Office Prof ...

  5. slow-log 和bin-log相关参数介绍

    1. slow-log show global status Slow_queries   --------慢查询的次数,即查询的时间超过long_query_time设置的时间(不能修改) 配置文件 ...

  6. mac OS vi/vim 使用教程

    vi/vim 的使用 基本上 vi/vim 共分为三种模式 分别是 命令模式(Command mode) 输入模式(Insert mode) 底线命令模式(Last line mode) 命令模式: ...

  7. Flutter仿网易云音乐:播放界面

    写在前头 本来是要做一个仿网易云音乐的flutter项目,但是因为最近事情比较多,项目周期跨度会比较长,因此分几个步骤来完成.这是仿网易云音乐项目系列文章的第一篇.没有完全照搬网易云音乐的UI,借鉴了 ...

  8. 【10月新版】Aspose.Pdf 10月新版V17.10发布 | 附下载

    2019独角兽企业重金招聘Python工程师标准>>> Aspose.Pdf for .NET 17.10 更新 功能和改进 核心 概述 类别 PDFNET-38067 支持DICO ...

  9. CSS3轻松实现彩色旋转六面体动画

    一.效果预览: 二.基本思路: 1.首先这个一个自动触发的动画,因此应使用animation设计,包括自动组装和组装完成后自动旋转的过程: 2.当鼠标放上去的时候六个面及上面的字体均变色,应在六个面设 ...

  10. JavaScript实现插入排序

    一.插入排序简介: 想象我们斗地主,摸排阶段,手里的牌都按照从小到大排序.如果每摸一张牌,我们就把他插入合适的位置,使得它比后面位置的牌小,比前面位置的牌大或者相等. 类似这样的一种排序方法就是插入排 ...