并发容器(Map、List、Set)实战及其原理

news/2024/5/20 8:11:10 标签: 数据结构, juc, 高并发

一. JUC包下的并发容器

        Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map,大家熟知的这些集合类ArrayList、LinkedList、HashMap这些容器都是非线程安全的。 所以,Java先提供了同步容器供用户使用。 同步容器可以简单地理解为通过synchronized来实现同步的容器 ,比如Vector、Hashtable以及SynchronizedList等容器。这样做的代价是削弱了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会降低。
        因此为了解决同步容器的性能问题,所以才有了并发容器。 java.util.concurrent 包中提供了多种并发类容器:
       

1.1 CopyOnWriteArrayList

        1.1.1 引入

        当我们遍历数组时加入,这时候有另一个线程也在往里添加数据的话,会报错如图:

        所以当我们使用  CopyOnWriteArrayList 时就可以解决
package com.laoyang.Thread.JUC包下的并发容器;

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author:Kevin
 * @create: 2023-10-19 16:47
 * @Description: 对应ArrayList
 */

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {

        CopyOnWriteArrayList arrayList = new CopyOnWriteArrayList();

//        ArrayList arrayList = new ArrayList();
        arrayList.add("小杨");
        arrayList.add("小凯");
        arrayList.add("小文");

        new Thread(() -> {
           arrayList.add("test");
        }).start();
        for (Object o : arrayList) {
            System.out.println(o.toString());
        }


    }
}

        概念

        CopyOnWriteArrayList 是 Java 中的一种线程安全的 List,它是一个可变的数组,支持并发读和写。与普通的 ArrayList 不同,它的读取操作不需要加锁,因此具有很高的并发性能。

        1.1.2 场景

        CopyOnWriteArrayList 的应用场景主要有两个方面:

        1. 读多写少的场景
        由于 CopyOnWriteArrayList 的读操作不需要加锁,因此它非常 适合在读多写少的场景中使用 。例如,一个读取频率比写入频率高得多的缓存,使用 CopyOnWriteArrayList 可以提高读取性能,并减少锁竞争的开销。
        2. 不需要实时更新的数据
        由于 CopyOnWriteArrayList 读取的数据可能不是最新的,因此它适合于不需要实时更新的数据。例如,在日志应用中,为了保证应用的性能,日志记录的操作可能被缓冲,并不是实时写入文件系统,而是在某个时刻批量写入。这种情况下,使用 CopyOnWriteArrayList 可以避免多个线程之间的竞争,提高应用的性能。

        1.1.3 黑名单实战

package com.laoyang.Thread.JUC包下的并发容器;

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author:Kevin
 * @create: 2023-10-19 19:35
 * @Description: 黑名单实战
 */

public class IpDemo {

    private static final CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

    //添加默认黑名单
    static {
        list.add("Ip0");
        list.add("Ip1");
        list.add("Ip2");

    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //模拟接入
                try {
                    Thread.sleep(new Random().nextInt(5000));
                }catch (Exception e){}
                String currentIP = "Ip" + new Random().nextInt(3);

                if (list.contains(currentIP)){
                    System.out.println(Thread.currentThread().getName() + " IP " +
                            currentIP + "命中黑名单,拒绝接入处理");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + " IP " +
                        currentIP + "接入处理...");
            }
        };

        new Thread(runnable, "thread1").start();
        new Thread(runnable, "thread2").start();
        new Thread(runnable, "thread3").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(new Random().nextInt(2000));
                }catch (Exception e){}

                String ip = "Ip3";
                list.add(ip);
                System.out.println(Thread.currentThread().getName() + " 添加了新的非法" +
                        ip+ " + newBlackIP");
            }
        }).start();
    }
}

        

1.1.4 CopyOnWriteArrayList原理

        CopyOnWriteArrayList 内部使用了一种称为“写时复制”的机制。当需要进行写操作时,它会创建一个新的数组,并将原始数组的内容复制到新数组中,然后进行写操作。因此,读操作不会被写操作阻塞,读操作返回的结果可能不是最新的,但是对于许多应用场景来说,这是可以接受的。此外,由于读操作不需要加锁,因此它可以支持更高的并发度。

        1.1.5 CopyOnWriteArrayList 的缺陷

        由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致 young gc 或者 full gc
        不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的,虽然 CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;
        CopyOnWriteArrayList 合适读多写少的场景 ,不过这类慎用。因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

http://www.niftyadmin.cn/n/5105520.html

相关文章

优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. 运算符

&#x1f974;博主&#xff1a;小猫娃来啦 &#x1f974;文章核心&#xff1a;优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. 运算符 文章目录 引言空值处理的挑战解决方案1&#xff1a;?? 运算符基本用法与 || 运算符的区别实际应用场景举例 解决方案2&#xff…

根据SpringBoot Guides完成进行示例学习(详细步骤)

目录 1.打开Spring | Guides官网&#xff0c;或者直接搜索springboot都可 2.选择要学习的内容 3.根据提示的网址&#xff0c;Git到本地 4.将文件用IDEA打开&#xff0c;根据教程完成示例&#xff0c;这里不做细致讲解 5.运行项目 6.在终端查看运行结果 以Scheduling Task…

国密sm2的Vue、Python、Java互通使用

目录 一、Vue 二、Python 三、Java 一、Vue # npm install --save sm-cryptoimport {sm2} from sm-crypto const cipherMode 1 const private_key d9d37f4f46e8514c6f9398a984e74f3eead994e8f4ac5f92e5deb313cb5ad6a6 const public_key 04 e332ee43ac37be458550652fb9…

中文编程开发语言工具开发案例:多种称重方式编程实际例子

中文编程开发语言工具开发案例&#xff1a;多种称重方式编程实际例子 上图为 计价秤&#xff0c;使用串口通讯线连接电脑的主机&#xff0c;软件自动读取称的重量&#xff0c;自动计算金额。这种方式称重快速&#xff0c;不需再打印条码。 上图这个称重方式为 一体称称重&#…

IDEA使用内置database数据库连接mysql报错:javax.net.ssl.SSLHandshakeException

参考一些博客的方式&#xff1a; 使用idea内置database连接数据库报错javax.net.ssl.SSLHandshakeException: No appropriate protocol_idea database ssl_你当像山的博客-CSDN博客 他们的方式是&#xff1a;在url后添加useSSLfalse 介绍另外一种方式&#xff1a; 点击datab…

用来生成二维矩阵的dcgan

有大量二维矩阵作为样本&#xff0c;为连续数据。数据具有空间连续性&#xff0c;因此用卷积网络&#xff0c;通过dcgan生成二维矩阵。因为是连续变量&#xff0c;因此损失采用nn.MSELoss()。 import torch import torch.nn as nn import torch.optim as optim import numpy a…

【Qt】常见控件

文章目录 按钮组QListWidget列表容器TreeWidget树控件TableWidget 表格控件其它控件介绍下拉框QLabel显示图片和动图 自定义控件封装 按钮组 QPushButton 常用按钮 QToolButton 工具按钮&#xff1a; 用于显示图片 如果想显示文字&#xff1a;修改风格&#xff1a;toolButto…

自定义反序列化类将LocalDate时间格式转为 LocalDateTime

从前端接收数据反序列化成类,如果时间格式不一致可能会反序列化失败 public class StorageDTO implements Serializable {private static final long serialVersionUID 1L;......//实体类中格式为JsonFormat(pattern "yyyy-MM-dd")JsonDeserialize(using CustomL…