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. Gatling脚本编写技巧篇(二)

    脚本示例: import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.durati ...

  2. Java中BigDecimal类

    由于在运算的时候,float类型和double很容易丢失精度,演示案例,所以,为了能精确地表示.计算浮点数,Java提供了BIgDecimal BigDecimal类的概述 不可变的.任意精度的有符号 ...

  3. JDBC 中的事务和批处理 batch

    JDBC事务处理: 事务处理一般在事务开始前把事务提交设置为false 所有DML语句执行完成后提交事务 demo: package com.xzlf.jdbc; import java.sql.Co ...

  4. 从零开始学习docker之在docker中搭建redis(单机)

    docker搭建redis 一.环境准备 云环境:CentOS 7.6 64位 二.下载镜像 从docker hub中找到redis镜像 传送门------https://hub.docker.com ...

  5. nav破解

    https://blog.csdn.net/qq_40529395/article/details/78839357

  6. 2019-2020-1 20199308《Linux内核原理与分析》第二周作业

    <Linux内核分析> 第一章 计算机工作原理 1.1 存储程序计算机工作模型 冯·诺依曼体系结构 各种计算机体系结构需要遵从的一个"客观规律" 结构图 冯·诺依曼体系 ...

  7. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之十(四十六)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  8. Scala教程之:深入理解协变和逆变

    文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...

  9. 修改mysql配置中my.conf中max_allowed_packet变量

    mysql根据配置文件会限制server接受的数据包大小. 有时候大的插入和更新会受max_allowed_packet 参数限制,导致写入或者更新失败. 查看目前配置 show VARIABLES ...

  10. 李宏毅机器学习--PM2.5预测

    一.说明 给定训练集train.csv,要求根据前9个小时的空气监测情况预测第10个小时的PM2.5含量. 训练集介绍: (1).CSV文件,包含台湾丰原地区240天的气象观测资料(取每个月前20天的 ...