031_java.util.concurrent.CopyOnWriteArrayList

news2024/11/16 5:55:33

继承体系

image.png
CopyOnWriteArrayList存在的目的是为了解决在高并发下list的读写。设计上希望只阻塞写行为,不会阻塞读行为。CopyOnWriteArrayList设计就基于此,在内部含有ReentrantLock用作修改时加锁,CopyOnWriteArrayList下有很多可以写方法,但是只有一把锁,意味着同一个时间只会有一个写操作发生。在锁锁住期间,进行写操作期间,会将此数据整个拷贝一份进行修改,完毕瞬间才会进行更新数据,这样就可以保证读操作不会被阻塞。这么说来,这个工具类只保证最终一致性。

重要变量

// 重入锁,读写并发的支持
final transient ReentrantLock lock = new ReentrantLock();
// 存储的真实的地方,只允许getArray/setArray方法对此访问
private transient volatile Object[] array;

// 用来cas操作的unsafe类
private static final sun.misc.Unsafe UNSAFE;
// lock重入锁的偏移量
private static final long lockOffset;

构造方法

public CopyOnWriteArrayList() {
    //默认调用setArray赋值空数组
    setArray(new Object[0]);
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    //如果当前传入的是CopyOnWriteArrayList集合,则直接得到这个类的Array赋值给当前类
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        // 否则得到数组,确保转换之后得到Object数组
        elements = c.toArray();
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    // 对当前数组数据进行赋值
    setArray(elements);
}

public CopyOnWriteArrayList(E[] toCopyIn) {
    //将传入数组进行转换,变为对象数组
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

final void setArray(Object[] a) {
    array = a;
}

可以看到,构造函数实现还是很简单的,最终都是在调用setArray进行数据的赋值。

重要方法

新增元素

新增元素相关方法如下:

  • add(E e) 添加一个元素到末尾。
  • add(int index, E element) 在某一个位置添加一个元素
  • addIfAbsent(E 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();
    }
}

public void add(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取旧数组
        Object[] elements = getArray();
        int len = elements.length;
        // 检查是否越界, 可以等于len
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            // 如果插入的位置是最后一位
            // 那么拷贝一个n+1的数组, 其前n个元素与旧数组一致
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            // 如果插入的位置不是最后一位
            // 那么新建一个n+1的数组
            newElements = new Object[len + 1];
            // 拷贝旧数组前index的元素到新数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            // 将index及其之后的元素往后挪一位拷贝到新数组中
            // 这样正好index位置是空出来的
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        // 将元素放置在index处
        newElements[index] = element;
        setArray(newElements);
    } finally {
        // 释放锁
        lock.unlock();
    }
}

public boolean addIfAbsent(E e) {
    // 获取元素数组, 取名为快照
    Object[] snapshot = getArray();
    // 检查如果元素不存在,直接返回false
    // 如果存在再调用addIfAbsent()方法添加元素
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 重新获取旧数组
        Object[] current = getArray();
        int len = current.length;
        // 如果快照与刚获取的数组不一致
        // 说明有修改,判定修改后是否已经存在目标数据
        if (snapshot != current) {
            // 重新检查元素是否在刚获取的数组里
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                // 假定当前快照相似,在common范围内不相等的数据位置数据和目标进行比对
                // 如果相等,意味着已经存在在数据里面,返回false
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        // 拷贝一份n+1的数组
        Object[] newElements = Arrays.copyOf(current, len + 1);
        // 将元素放在最后一位
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

删除元素

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)
            // 如果移除的是最后一位
            // 那么直接拷贝一份n-1的新数组, 最后一位就自动删除了
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 如果移除的不是最后一位
            // 那么新建一个n-1的新数组
            Object[] newElements = new Object[len - 1];
            // 将前index的元素拷贝到新数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            // 将index后面(不包含)的元素往前挪一位
            // 这样正好把index位置覆盖掉了, 相当于删除了
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

public boolean remove(Object o) {
    Object[] snapshot = getArray();
    // 从0-length范围内寻找o对象
    int index = indexOf(o, snapshot, 0, snapshot.length);
    // 从index之后的范围内删除o对象
    return (index < 0) ? false : remove(o, snapshot, index);
}

private boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) findIndex: {
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                if (index >= len)
                    return false;
                if (current[index] == o)
                    break findIndex;
                index = indexOf(o, current, index, len);
                if (index < 0)
                    return false;
            }
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

变更元素

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获得数据
        Object[] elements = getArray();
        // 获得index位置的数据
        E oldValue = get(elements, index);

        // 拷贝一份数据出来,将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();
    }
}

查询元素

查询元素的方法有以下几个:

  • get(intindex) 根据索引位置获得元素
  • indexOf(E e,int index) 根据元素,范围,查找元素所在的索引位置
  • indexOf(Object o) 根据元素,查找元素所在的索引范围
  • lastIndexOf(Object o) 根据元素,从尾部开始查找元素所在的索引范围
  • lastIndexOf(E e,int index) 根据元素,范围,从尾部开始查找元素所在的索引位置

public E get(int index) {
    // 获取元素不需要加锁
    // 直接返回index位置的元素
    // 这里是没有做越界检查的, 因为数组本身会做越界检查
    return get(getArray(), index);
}

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

public int indexOf(E e, int index) {
    Object[] elements = getArray();
    return indexOf(e, elements, index, elements.length);
}

public int indexOf(Object o) {
    Object[] elements = getArray();
    return indexOf(o, elements, 0, elements.length);
}

private static int indexOf(Object o, Object[] elements,
                           int index, int fence) {
    if (o == null) {
        for (int i = index; i < fence; i++)
            if (elements[i] == null)
                return i;
    } else {
        for (int i = index; i < fence; i++)
            if (o.equals(elements[i]))
                return i;
    }
    return -1;
}

public int lastIndexOf(Object o) {
    Object[] elements = getArray();
    return lastIndexOf(o, elements, elements.length - 1);
}

public int lastIndexOf(E e, int index) {
    Object[] elements = getArray();
    return lastIndexOf(e, elements, index);
}

private static int lastIndexOf(Object o, Object[] elements, int index) {
    if (o == null) {
        for (int i = index; i >= 0; i--)
            if (elements[i] == null)
                return i;
    } else {
        for (int i = index; i >= 0; i--)
            if (o.equals(elements[i]))
                return i;
    }
    return -1;
}

CopyOnWriteArraySet

CopyOnWriteArraySet是线程安全的集合操作,继承自AbstractSet:
image.png
其底层使用CopyOnWriteList进行存储,所有操作都转交给了CopyOnWriteList。源码相对简单,在这里不过多描述。

总结

CopyOnWriteList是ArrayList的线程安全版本,其设计上将读写进行分离,只会对写操作进行阻塞,其内部使用ReentrantLock确保线程安全。CopyOnWriteList遇到写操作的时候都会将数据重新复制一份在此基础上进行修改最终将新拷贝的数据覆盖原来的数据。因此如果您的场景是读多写少,且数据不会很大的时候,CopyOnWriteList会很方便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1989980.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三种向量相似度计量方法——欧式距离、余弦相似度、皮尔逊相关系数

1、欧式距离 欧氏距离在机器学习可以清晰展示不同对象的相似程度。 欧式距离是最直观的距离度量方法之一&#xff0c;它衡量两个点之间的直线距离, 较小的欧式距离意味着较高的相似度。 分类——K近邻算法&#xff08;KNN&#xff09;&#xff1a;需要对一个新的样本进行分类…

IIS6 PUT漏洞

一.漏洞描述 IIS Server 在 Web 服务扩展中开启了 WebDAV &#xff0c;配置了可以写⼊的权限&#xff0c;造成任意⽂件上传 1.1环境搭建 环境 fofa&#xff1a;"IIS-6.0" 本地搭建2003 server 1.2漏洞复现 1.开启 WebDAV 和写权限&#xff1a; 1.3 漏洞复现 使…

随笔(三)——项目代码优化

文章目录 一、数据驱动的优化点0.项目技术1.需求说明2. 优化前3.优化后&#xff08;复杂版&#xff09;4.优化后&#xff08;可读性高版&#xff09; 二、使用循环遍历&#xff0c;减少if-else1.源代码2. 优化后3. 优点 一、数据驱动的优化点 0.项目技术 vue2 view design …

OpenAI 推出 gpt-4o-2024-08-06 模型 解析结构化输出功能 附体验平台

人工智能技术的边界再次被突破&#xff0c;OpenAI 社区迎来了 gpt-4o-2024-08-06 模型的问世&#xff0c;这不仅是一次技术的飞跃&#xff0c;更是对智能助手功能和可靠性的一次全面革新。 技术革新&#xff1a;gpt-4o-2024-08-06 模型的诞生 gpt-4o-2024-08-06 模型是 OpenA…

【深度学习】用Pytorch完成MNIST手写数字数据集的训练和测试

模型训练相关 思路&#xff1a; 导入数据集&#xff08;对数据集转换为张量&#xff09;加载数据集&#xff08;使数据集成为可以进行迭代&#xff09;搭建卷积模型进行模型训练&#xff08;每训练一轮查看一次在测试集上的准确率&#xff09;使用tensorboard进行可视化保存训…

MySQL3 DQL数据查询语言

DQL SQL-DQL重要地位简单查询selectjia简单查询数据准备别名(AS)消除重复行(DISTINCT去重)算数运算符0.优先级1.算数运算符2.比较运算符3.逻辑运算符4.位运算符 空值空值参与运算 条件查询普通条件查询特殊比较运算符BETWEEN...AND...INLIKEIS NULLleast&#xff0c;greatest运…

Unity补完计划 之 SpriteEditer SingleMode

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 因为unity不只是3d需要&#xff0c;还有2d游戏需要大量编辑处理图片素材&#xff0c;所以需要了解Sprite&#xff08;精灵…

ASC格式的协议数据解析

函数来自RTT的AT组件 - at_client.c 例如&#xff0c;数据是 CGREG: 0,1&#xff0c;通过at_resp_parse_line_args_by_kw把1赋予link_stat。 简化从AT响应中提取信息的过程&#xff0c;使得编写与硬件通信的代码更加简洁和易于维护。 这么提数据也太方便了 at_resp_parse_l…

结构体练习作业

作业一:结构体数组存储学生信息(姓名&#xff0c;年龄&#xff0c;分数)&#xff0c;完成输入学生信息&#xff0c;输出学生信息&#xff0c;求学生成绩之和&#xff0c;求最低学生成绩。 .h文件 main.c .c文件 输入信息 输出信息 平均值 最低值 作业二:在堆区&#xff0c;申…

STC-ISP升级MCU

STC-ISP升级mcu步骤&#xff1a; 1、RS232线连接电脑&#xff0c;芯片型号选择STC8H8K64U 2、波特率选择115200 3、IRC频率选择24MHz 4、设置EEPROM大小为64K 如下图设置&#xff1a; 插上RS232选择相应的COM口&#xff1a; 我这里的COM口是COM5. 打开程序文件&#xff1…

揭秘Redis的“隐藏武器”:跳跃表的原理与应用

1. 引言 1.1 Redis的快速崛起 Redis&#xff0c;全名为Remote Dictionary Server&#xff0c;是一个开源的高性能键值对存储系统&#xff0c;它提供了多种类型的数据结构&#xff0c;如字符串、列表、集合、有序集合等。由于其高性能、持久化选项以及丰富的特性&#xff0c;Re…

【已解决】如何获取到DF数据里最新的调薪时间,就是薪资最高且时间最早?

问题说明&#xff1a; 前几天在Python最强王者交流群【群除我佬】问了一个Pandas处理的问题&#xff0c;这里拿出来给大家分享下。 看上去不太好理解&#xff0c;其实说白了&#xff0c;就是在工资最高里&#xff0c;再找时间最早的。 换句话说就是&#xff0c;这三个人&…

益九未来CEO曾宪军:创新引领,打造智能售货机行业新标杆

在智能零售行业迅速发展的今天&#xff0c;益九未来&#xff08;天津&#xff09;科技发展有限公司正以其创新精神和前瞻性的战略布局&#xff0c;引领着智能售货机市场的潮流。而这一切的背后&#xff0c;离不开总经理&#xff08;CEO&#xff09;曾宪军先生的卓越领导和远见卓…

人类预期寿命数据-1960至2022年(世界各国与中国各省)

数据简介&#xff1a;人类预期寿命是指在特定年龄出生的人群&#xff0c;按照当前的死亡率水平&#xff0c;预期平均能够存活的年数。预期寿命衡量一个国家和地区卫生健康状况、社会经济发展水平和生活条件的重要参数&#xff0c;这次数据包含世界各国&#xff08;1960-2022年&…

代理IP类型详细解析:那么多种协议的代理如何选?

代理IP已经成为跨境业务的得力工具&#xff0c;但是仍有许多新手小白在初次接触到代理IP服务商时&#xff0c;不知道具体如何选择代理IP类型&#xff0c;面对五花八门的代理类型名称&#xff0c;往往需要付出一定的试错成本才知道哪个适合自己的业务。今天就来给大家科普科普&a…

深度学习中的规范化-层规范化

文章目录 层规范化层规范化参数与公式normalized_shape传入一个整数接口函数LayerNorm计算手动计算 normalized_shape传入一个列表接口函数LayerNorm计算手动计算 层规范化 在批量规范化这篇文章里详细介绍了批量规范化在卷积神经网络里的使用&#xff0c;本篇文章将继续介绍另…

LVS中NAT模式和DR模式实战讲解

1DR模式 DR&#xff1a;Direct Routing&#xff0c;直接路由&#xff0c;LVS默认模式,应用最广泛,通过为请求报文重新封装一个MAC首部进行 转发&#xff0c;源MAC是DIP所在的接口的MAC&#xff0c;目标MAC是某挑选出的RS的RIP所在接口的MAC地址&#xff1b;源 IP/PORT&#xf…

C++:auto关键字、内联函数、引用、带默认形参值的函数、函数重载

一、auto关键字 在C中&#xff0c;auto关键字是一个类型说明符&#xff0c;用于自动类型推导。 使用 auto 关键字时&#xff0c;变量的类型 是在编译时由编译器 根据 初始化表达式 自动推导出来的。这意味着你 不能在 声明 auto 变量时 不进行初始化 声明 auto 变量时&#x…

“八股文”:是助力还是阻力?

在程序员面试中&#xff0c;“八股文”是一个绕不开的话题。所谓“八股文”&#xff0c;指的是那些在面试中经常出现的标准问题及其答案&#xff0c;例如“解释一下死锁的概念”、“CAP理论是什么”等。这些内容通常被求职者反复练习&#xff0c;以至于变成了某种固定的模式或套…