Java——多线程高并发系列之ArrayList、HashSet、HashMap集合线程不安全的解决方案

简介: Java——多线程高并发系列之ArrayList、HashSet、HashMap集合线程不安全的解决方案

1.ArrayList的线程不安全解决方案


main方法的第一行注释打开,多执行几次,会看到如下图这样的异常信息:👇👇👇


这是一个并发修改异常,首先ArrayList肯定是线程不安全的,产生这个异常的原因就是可能第一个线程刚进入ArrayList 集合中要进行add 操作时,另外一个线程此时也进来进行 add 操作,而第三个线程又进来进行 get 操作,导致读写没办法进行同步了,最终打印结果的时候就炸了。

解决方案看代码中的剩下几行注释。

package test.notsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
 * 演示ArrayList的线程不安全问题及解决方案
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        //List<String> list = new ArrayList<>();
        //解决方法1:使用Vector
        //List<String> list = new Vector<>();
        //解决方法2:Collections
        //List<String> list = Collections.synchronizedList(new ArrayList<>());
        //解决方法3:CopyOnWriteArrayList
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}


关于 CopyOnWriteArrayList 解决线程不安全问题的简单解释:就看源码中的add(E e) 这个方法:

    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();
        }
    }

这个 CopyOnWriteArrayList 在进行 add 添加操作之前,先进行lock 上锁,然后通过 getArray() 获取到原 ArrayList 集合容器,之后调用 Arrays.copyOf 方法将原容器拷贝出一个新容器,因为要添加(长度自然也要+1),之后向这个新容器中添加元素,添加完成之后,调用 setArray 方法将原容器的引用指向了这个新的容器。    那么这样做的好处就是:添加元素在新容器中,原容器该是啥样还是啥样,其他线程要get读取元素就还从原容器中读(即多个线程可以进行并发读);而其他线程要 add 添加,要等待其他线程完成之后,将原容器的引用指向新容器就可以了。


CopyOnWrite 容器在面对读和写的时候是两个不同的容器,也是用到了读写分离的思想。

2.HashSet的线程不安全解决方案


这里如果是 new HashSet 了话,仍然可能出现向上面 ArrayList 一样的并发修改异常。解决方案看代码中的注释。

package test.notsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
 * 演示HashSet的线程不安全问题及解决方案
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        //Set<String> set = new HashSet<>();
        //解决方法1:Collections
        //Set<String> set = Collections.synchronizedSet(new HashSet<>());
        //解决方法2:CopyOnWriteArraySet
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

3.HashMap的线程不安全解决方案


package test.notsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 演示HashMap的线程不安全问题及解决方案
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        //Map<String,Object> map = new HashMap<>();
        //解决方法1:Collections
        //Map<String,Object> map = Collections.synchronizedMap(new HashMap<>());
        //解决方法2:ConcurrentHashMap
        Map<String,Object> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            String key = String.valueOf(i);
            new Thread(() -> {
                map.put(key,UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}
相关文章
|
11月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
448 0
|
9月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
816 5
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
596 3
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
219 1
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
264 1
|
Java
让星星⭐月亮告诉你,HashMap中保证红黑树根节点一定是对应链表头节点moveRootToFront()方法源码解读
当红黑树的根节点不是其对应链表的头节点时,通过调整指针的方式将其移动至链表头部。具体步骤包括:从链表中移除根节点,更新根节点及其前后节点的指针,确保根节点成为新的头节点,并保持链表结构的完整性。此过程在Java的`HashMap$TreeNode.moveRootToFront()`方法中实现,确保了高效的数据访问与管理。
199 2
|
Java 索引
让星星⭐月亮告诉你,HashMap之往红黑树添加元素-putTreeVal方法源码解读
本文详细解析了Java `HashMap` 中 `putTreeVal` 方法的源码,该方法用于在红黑树中添加元素。当数组索引位置已存在红黑树类型的元素时,会调用此方法。具体步骤包括:从根节点开始遍历红黑树,找到合适位置插入新元素,调整节点指针,保持红黑树平衡,并确保根节点是链表头节点。通过源码解析,帮助读者深入理解 `HashMap` 的内部实现机制。
298 2
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
319 0
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
550 3

热门文章

最新文章