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

news/2024/7/16 7:11:37

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

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

相关文章

使用aspectJ实现Spring AOP的两种方式

方式一:基于aspectJ的XML配置 方式二:基于aspectJ的注解方式 基于aspectJ的XML配置 1) 引入相关jar包 2) 创建Spring核心配置文件,必须导入aop的约束 <?xml version"1.0" encoding"UTF-8"?><beans xmlns"http://www.springfra…

Java——多线程高并发系列之JUC三大辅助类(CountDownLatch、CyclicBarrier、Semaphore)

写在前面 JUC 中提供了三种常用的辅助类&#xff0c;通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为&#xff1a; CountDownLatch: 减少计数CyclicBarrier: 循环栅栏Semaphore: 信号灯1.CountDownLatch CountDownLatch 类可以设置一个计数器&a…

任何一门语言思考的

1、第一个hellowolrd程序。【输出】 2、交互程序。【如何从文件读取&#xff0c;从键盘读取&#xff0c;输出到文件&#xff0c;输出到屏幕。IO】 3、for循环&#xff0c;break&#xff0c;continue等【流程控制程序】 4、函数调用方面【如何函数调用&#xff0c;模块调用】 4、…

Java——多线程高并发系列之Fork/Join框架简单应用

1.Fork/Join框架简介 Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理&#xff0c;最后将子任务结果合并成最后的计算结果&#xff0c;并进行输出。Fork/Join 框架要完成两件事情&#xff1a; Fork&#xff1a;把一个复杂任务进行分拆&#xff0c;大事化小 &…

DWR推技术在开发中需要注意的ScriptSession问题

2019独角兽企业重金招聘Python工程师标准>>> 1 关于ScriptSessionScriptSession不会与HttpSession同时创建当我们访问一个页面的时候&#xff0c;如果是第一次访问&#xff0c;就会创建一个新的HttpSession,之后再访问的时候&#xff0c;就会保持当前的Session,即使…

MinGW 介绍

SDL新手教程&#xff08;一&#xff09;&#xff1a;3、MinGW 下的安装与设置 作者&#xff1a;龙飞3.1&#xff1a;MinGW 是什么&#xff1f; MinGW 提供了一套简单方便的Windows下的基于GCC 程序开发环境。MinGW 收集了一系列免费的Windows 使用的头文件和库文件&…

input单选按钮样式修改

1、css input[type"radio"] label::before {content: "\a0"; /*不换行空格*/ display: inline-block; vertical-align: middle;width: 1.2vh;height: 1.2vh;margin-right: 0.5vh;border-radius: 50%;border: 1px solid #afbcc8;padding: 1px;} input[type…

RabbitMQ——消息手动应答、队列/消息持久化、不公平分发、预取值的概念理解及应用举例

1.消息应答 消费者完成一个任务可能需要一段时间&#xff0c;如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了&#xff0c;会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息&#xff0c;便立即将该消息标记为删除。在这种情况下&#xff0c;突然有个消费…