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. # Unity 游戏框架搭建 2019 (三十四、三十五) 9 ~ 10 示例整理

    第九个示例 目前代码如下: using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace QFramework { p ...

  2. SpringCloud-Config 配置中心

    概述 分布式系统面临的问题 微服务意味着要将单体应用中的业务拆分成一个个的子服务,这些服务都需要必要的配置信息才能运行,如果有上百个微服务,上百个配置文件,管理起来是非常困难的,这时候,一套集中式的. ...

  3. <algorithm>中常用函数

    先说一下STL操作的区间是 [a, b),左边是闭区间,右边是开区间,这是STL的特性,所以<algorithm>里面的函数操作的区间也都是 [a, b). 先声明一下, sort()函数 ...

  4. phpspider框架的使用

    手册:https://doc.phpspider.org/configs-members.html 参考:https://www.jianshu.com/p/01052508ea7c 不多说,代码贴上 ...

  5. 2019-2020-1 20199328《Linux内核原理与分析》第九周作业

    笔记部分 2019/11/12 14:45:44 从CPU和内存的角度看linux系统的运行 CPU角度:首先我们进行了系统调度,然后系统进入内核态,把信息压栈,然后我们进行进程管理,由于进入系统调用 ...

  6. Spring Cloud 系列之 Stream 消息驱动(一)

    在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,消息中间件解决了应用解耦.异步处理.流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构. 不同中间件内部实现方式是不一样的,这些中间 ...

  7. 【杂谈】从实现角度看ChannelFuture

    JDK中的Future特性 在介绍Netty的ChannelFuture之前,我们先来看看JDK中的Future是如何实现的.总的来说就是任务提交的时候会使用装饰器模式,将任务包装成一个FutureT ...

  8. 利用requests, beautifulsoup包爬取股票信息网站

    这是第一次用requests, beautifulsoup实现爬虫,此次爬取的是一个股票信息网站:http://www.gupiaozhishi.net.cn. 实现非常简单,只是为了demo使用的数 ...

  9. bdc抢夺域控

    1.运行CMD2.在 ntdsutil :提示符下输入 ntdsutil3.在 ntdsutil :提示符下输入 roles4.在 fsmo maintenance:提示符下输入 connection ...

  10. LinearLayout控件

    LinearLayout是线性布局控件,它包含的子控件将以横向或竖向的方式排列,按照相对位置来排列所有的widgets或者其他的containers,超过边界时,某些控件将缺失或消失.因此一个垂直列表 ...