• 写时复制原理(copy-on-write)


    CopyOnWrite特点

    • 一般用于读多写少的情况,用于提高读取数据的效率
    • 注意的是,读数据并不能保证实时性,因为读取时,读取的是旧数组的数据
    • 缺点是:占用内存(每添加一次就需要复制一个数据)和数据一致性问题(不能保证实时数据)

    以CopyOnWriteArrayList源码进行分析

    属性

    // 显式操作的重入锁对象
    final transient ReentrantLock lock = new ReentrantLock();
    // 用来存储元素的对象数组
    private transient volatile Object[] array;
    

    构造器

    // 默认构造器
    public CopyOnWriteArrayList() {
        setArray(new Object[0]); // 数组长度为0
    }
    
    // 给定一个Collection容器来构建CopyOnWriteArrayList实例
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }
    
    // 给定一个数组来构建CopyOnWriteArrayList实例
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
    
    

    写方法【写时复制】

    add(E e) - 添加一个元素

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 上锁
        lock.lock();
        try {
            // 获取对象数组
            Object[] elements = getArray(); // =========== 旧对象数组
            int len = elements.length;
            // 复制原来的对象数组,并返回一个比源对象数组长度 +1 的对象数组 
            Object[] newElements = Arrays.copyOf(elements, len + 1); // =========== 新对象数组
            // 将元素添加到对象数组的末尾
            newElements[len] = e;
            // 替换内部对象数组的引用
            setArray(newElements);
            return true;
        } finally {
            // 解锁
            lock.unlock();
        }
    }
    

    add(int index, E element) - 添加一个元素到指定位置

    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]; // =========== 新对象数组
                // 将elements下标为0开始的元素复制到newElements下标为[0,index)处
                System.arraycopy(elements, 0, newElements, 0, index);
                // 将elements下标为index开始的元素复制到newElements下标为[index+1,numMoved)处
                System.arraycopy(elements, index, newElements, index + 1,numMoved);
            }
            // 将element插入到newElements的index处
            newElements[index] = element;
            // 替换内部对象数组的引用
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
    

    set(int index, E element) - 替换指定位置的元素

    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray(); // =========== 旧对象数组
            // index位置的旧元素
            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();
        }
    }
    

    读方法

    final Object[] getArray() {
        return array; // 获取内部对象数组
    }
    
    // 根据下标获取元素
    public E get(int index) {
        return get(getArray(), index);
    }
    
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    

    使用测试

    public class Main {
    
    	public static void main(String[] args) {
    
    		CopyOnWriteArrayList<String> cow = new CopyOnWriteArrayList<String>();
    		cow.add("a1");
    		cow.add("a2");
    		cow.add(1, "a1.5");
    		System.out.println("=====" + cow + "=====");
    		
    		cow.set(1, "hehe");
    		System.out.println("=====" + cow + "=====");
    		
    		/**
    		 * output:
    				=====[a1, a1.5, a2]=====
    				=====[a1, hehe, a2]=====
    		 */
    		
    	}
    }
    
    
  • 相关阅读:
    MyCLI :一个支持自动补全和语法高亮的 MySQL/MariaDB 客户端
    pathlib:优雅的路径处理库
    MySQL索引连环18问
    Mysql 百万级数据迁移实战笔记
    强大的Json解析工具 Jsonpath 实战教程
    JavaScript 中的 Var,Let 和 Const 有什么区别
    【前端安全】从需求分析开始,详解前端加密与验签实践
    vue3开发企业级生鲜系统项目
    mysql随笔
    shiro相关Filter
  • 原文地址:https://www.cnblogs.com/tandi19960505/p/9890336.html
Copyright © 2020-2023  润新知