List集合多线程并发条件下不安全如何解决

目录
  • 前言
  • 一、List集合使用模拟并发测试
    • 1.1 单线程环境下
    • 1.2 多线程环境下
  • 二、解决方案
    • 2.1 使用Vector类
    • 2.1 使用Collections.synchronizedList
    • 2.3 使用并发容器CopyOnWriteArrayList
  • 总结

前言

在日常开发过程中,List是我们常用的集合,比如查询数据库内容返回值比会用一个集合来装,但是在多线程并发的条件下,会出现安全问题吗?下面我们就来测试一下,如果出现安全问题,该如何解决.

一、List集合使用模拟并发测试

1.1 单线程环境下

public static void main(String[] args) {
    // List集合
    List<String> list = new ArrayList<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
    }
}

可以看到单线程条件下,我们做list的插入操作完全没问题,下面我们来模拟并发条件下执行,会出现什么问题。

1.2 多线程环境下

public static void main(String[] args) {
    // List集合
    List<String> list = new ArrayList<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常,就是并发修改异常。

二、解决方案

2.1 使用Vector类

public static void main(String[] args) {
    // List集合
    List<String> list = new Vector<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

Vector 是同步访问的,它的add方法底层加了synchronized关键字修饰。

测试结果:

2.1 使用Collections.synchronizedList

public static void main(String[] args) {
   // List集合
    List<String> list = Collections.synchronizedList(new ArrayList<>());
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

查看底层源码可以发现他也使用了synchronized关键字修饰。

2.3 使用并发容器CopyOnWriteArrayList

public static void main(String[] args) {
    // List集合
    List<String> list = new CopyOnWriteArrayList<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

查看源码它使用的是lock锁机制。

写入时复制,有多个线程调用的时候,写入的时候,复制一份,避免覆盖造成数据问题。就是在写的时候不对原集合进行修改,而是重新复制一份,修改完之后,再移动指针。

从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

解读源码:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
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);//拷贝新数组
        newElements[len] = e;
        setArray(newElements);//将引用指向新数组
        return true;
    } finally {
        lock.unlock();//解锁
    }
}

add()在添加集合的时候加上了锁,保证了同步,避免了多线程写的时候会Copy出N个副本出来。

总结

CopyOnWriteArrayList使用场景:读多写少(白名单,黑名单,商品类目的访问和更新场景),集合不大。所以一般来说,我们都会使用JUC包下给我们提供的线程安全容器,而不是使用老一代的线程安全容器。

到此这篇关于List集合多线程并发条件下不安全如何解决的文章就介绍到这了,更多相关List集合多线程并发条件下不安全 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程高并发中解决ArrayList与HashSet和HashMap不安全的方案

    1.ArrayList的线程不安全解决方案 将main方法的第一行注释打开,多执行几次,会看到如下图这样的异常信息:

  • List集合多线程并发条件下不安全如何解决

    目录 前言 一.List集合使用模拟并发测试 1.1 单线程环境下 1.2 多线程环境下 二.解决方案 2.1 使用Vector类 2.1 使用Collections.synchronizedList 2.3 使用并发容器CopyOnWriteArrayList 总结 前言 在日常开发过程中,List是我们常用的集合,比如查询数据库内容返回值比会用一个集合来装,但是在多线程并发的条件下,会出现安全问题吗?下面我们就来测试一下,如果出现安全问题,该如何解决. 一.List集合使用模拟并发测试 1.

  • Kotlin遍历集合导致并发修改异常的原因和解决方法

    各位android 老司机们,对于android 遍历结合的时候,发生并发修改异常一定毫不陌生: 之前看到过一篇文章, 在阿里巴巴Java开发手册中,有这样一条规定: 其实,增强for循环也是Java给我们提供的一个语法糖,如果将以上代码编译后的class文件进行反编译(使用jad工具)的话,可以得到以下代码: 1.原因:(其实我都不想在各位老司机面前再赘述这个了.-_-||) 这个异常产生的原因是,迭代器依赖于集合而存在,在判断成功后,集合中添加了新的元素,而迭代器并不知道,所有就报错了.其实

  • Redis高并发场景下秒杀超卖解决方案(秒杀场景)

    目录 1 什么是秒杀 2 为什么要防止超卖 3 单体架构常规秒杀 3.1 常规减库存代码 3.2 模拟高并发 3.3 超卖现象 3.4 分析原因 4 简单实现悲观乐观锁解决单体架构超卖 4.1 悲观锁 4.2 乐观锁 4.3 redis锁setnx 4.4 使用Redision 5 分布式锁的解决方案 6 采用缓存队列防止超卖 1 什么是秒杀 秒杀最直观的定义:在高并发场景下而下单某一个商品,这个过程就叫秒杀 [秒杀场景] 火车票抢票 双十一限购商品 热度高的明星演唱会门票 … 2 为什么要防止

  • Java多线程中不同条件下编写生产消费者模型方法介绍

    简介: 生产者.消费者模型是多线程编程的常见问题,最简单的一个生产者.一个消费者线程模型大多数人都能够写出来,但是一旦条件发生变化,我们就很容易掉进多线程的bug中.这篇文章主要讲解了生产者和消费者的数量,商品缓存位置数量,商品数量等多个条件的不同组合下,写出正确的生产者消费者模型的方法. 欢迎探讨,如有错误敬请指正 生产消费者模型 生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品.生产消费者模式

  • C++ 多线程编程建议之 C++ 对多线程/并发的支持(下)

    前言: 本文承接前文  C++ 对多线程/并发的支持(上) ,翻译自 C++ 之父 Bjarne Stroustrup 的 C++ 之旅(A Tour of C++)一书的第 13 章 Concurrency.本文将继续介绍 C++ 并发中的 future/promise,packaged_task 以及 async() 的用法. 1.通信任务 标准库还在头文件 <future> 中提供了一些机制,能够让编程人员基于更高的抽象层次任务来开发,而不是直接使用低层的线程.锁: future 和 p

  • Java 多线程并发编程_动力节点Java学院整理

    一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

  • Java 多线程并发AbstractQueuedSynchronizer详情

    目录 AbstractQueuedSynchronizer 核心思想 为什么需要 AQS 用法 用法示例 AQS 底层原理 父类 AbstractOwnableSynchronizer CLH 队列 Condition 用于等待的方法 用于唤醒的方法 ConditionObject Signalling methods Waiting methods enableWait canReacquire unlinkCancelledWaiters 对外提供的等待方法 awaitUninterrupt

  • Java多线程并发编程(互斥锁Reentrant Lock)

    Java 中的锁通常分为两种: 通过关键字 synchronized 获取的锁,我们称为同步锁,上一篇有介绍到:Java 多线程并发编程 Synchronized 关键字. java.util.concurrent(JUC)包里的锁,如通过继承接口 Lock 而实现的 ReentrantLock(互斥锁),继承 ReadWriteLock 实现的 ReentrantReadWriteLock(读写锁). 本篇主要介绍 ReentrantLock(互斥锁). ReentrantLock(互斥锁)

  • Java多线程并发编程和锁原理解析

    这篇文章主要介绍了Java多线程并发编程和锁原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等方案后,问题得到解决. 加锁方案见下文. 二.乐观锁 & 悲观锁 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁

  • 在IntelliJ IDEA中多线程并发代码的调试方法详解

    通常来说,多线程的并发及条件断点的debug是很难完成的,或许本篇文章会给你提供一个友好的调试方法.让你在多线程开发过程中的调试更加的有的放矢. 我们将通过一个例子来学习.在这里,我编写了一个多线程程序来计算此数学问题:100! + 100000!.即:100的阶乘 + 100000的阶乘. 数学不好的同学看这里,100 阶乘就是:1 * 2 * 3 * -- * 100 = ? ,简写为100! import java.math.BigInteger; public class MathPro

随机推荐