JUC面试(五)——Collection线程不安全

news2024/11/16 20:22:17

Collection线程不安全

前言

当我们执行下面语句的时候,底层进行了什么操作

new ArrayList<Integer>();

底层创建了一个空的数组,伴随着初始值为10

当执行add方法后,如果超过了10,那么会进行扩容,扩容的大小为原值的一半,也就是5个,使用下列方法扩容

Arrays.copyOf(elementData, netCapacity)

ArrayList线程不安全

单线程环境

单线程环境的ArrayList是不会有问题的

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

        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        for(String element : list) {
            System.out.println(element);
        }
    }
}

多线程环境

为什么ArrayList是线程不安全的?因为在进行写操作的时候,方法上为了保证并发性,是没有添加synchronized修饰,所以并发写的时候,就会出现问题

在这里插入图片描述

当我们同时启动30个线程去操作List的时候

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 集合类线程不安全举例
 * @author: wzq
 * @create: 2020-03-12-20:15
 */
public class ArrayListNotSafeDemo {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

这个时候出现了错误,也就是java.util.ConcurrentModificationException

在这里插入图片描述

这个异常是 并发修改的异常

解决方案

方案一:Vector

第一种方法,就是不用ArrayList这种不安全的List实现类,而采用Vector,线程安全的

关于Vector如何实现线程安全的,而是在方法上加了锁,即synchronized

在这里插入图片描述

这样就每次只能够一个线程进行操作,所以不会出现线程不安全的问题,但是因为加锁了,导致并发性极度下降

方案二:Collections.synchronizedList()

List<String> list = Collections.synchronizedList(new ArrayList<>());

采用Collections集合工具类,在ArrayList外面包装一层 同步 机制,加同步锁,导致并发性极度下降。

前2个方案的区别:

同步代码块

synchronized(obj){   
    //需要被同步的代码块
}

其中,obj 称为同步监视器,也就是锁,原理是:当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

  • Vector使用同步方法实现,synchronizedList使用同步代码块实现

    同步代码块和同步方法的区别 1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。 2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。 3.同步代码块可以选择对哪个对象加锁,但是同步方法只能给this对象加锁。

  • SynchronizedList有很好的扩展和兼容功能。它可以将所有的List的子类转成线程安全的类

  • 使用SynchronizedList的时候,进行遍历时要手动进行同步处理

  • SynchronizedList可以指定锁定的对象

方案三:采用JUC里面的方法

CopyOnWriteArrayList:写时复制,主要是一种读写分离的思想

List<String> list = new CopyOnWriteArrayList<>();

写时复制,CopyOnWrite容器即写时复制的容器,往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将Object[]进行copy,复制出一个新的容器object[] newElements,然后新的容器Object[] newElements里添加原始,添加元素完后,在将原容器的引用指向新的容器 setArray(newElements);这样做的好处是可以对copyOnWrite容器进行并发的度,而不需要加锁,因为当前容器不需要添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

就是写的时候,把ArrayList扩容一个出来,然后把值填写上去,在通知其他的线程,ArrayList的引用指向扩容后的新List。

CopyOnWriteArrayList虽然是一个线程安全版的ArrayList,但其每次修改数据时都会复制一份数据出来,所以只适用读多写少或无锁读场景。

高并发写时,CopyOnWriteArrayList为何这么慢呢?因为其每次add时,都用Arrays.copyOf创建新数组,频繁add时内存申请释放性能消耗大。

查看底层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();
        }
    }

首先需要加锁

final ReentrantLock lock = this.lock;
lock.lock();

然后在末尾扩容一个单位

Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);

然后在把扩容后的空间,填写上需要add的内容

newElements[len] = e;

最后把内容set到Array中

适合读多写少的情况

HashSet线程不安全

CopyOnWriteArraySet

底层还是使用CopyOnWriteArrayList进行实例化

在这里插入图片描述

HashSet底层结构

同理HashSet的底层结构就是HashMap

在这里插入图片描述

但是为什么我调用 HashSet.add()的方法,只需要传递一个元素,而HashMap是需要传递key-value键值对?

首先我们查看hashSet的add方法

   public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

我们能发现但我们调用add的时候,存储一个值进入map中,只是作为key进行存储,而value存储的是一个Object类型的常量,也就是说HashSet只关心key,而不关心value

HashMap线程不安全

HashMap底层,默认大小16(必须是2^n),加载因子默认0.75,0.75*容量=12,所以map容量达到13则扩容,大小可自定义。java8数据结构:数组+单向链表–>>节点>8红黑树。key相同,存进去会覆盖原value,但是其底层是链表,>8是红黑树。

同理HashMap在多线程环境下,也是不安全的

    public static void main(String[] args) {

        Map<String, String> map = new HashMap<>();

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }

在这里插入图片描述

解决方法

1、使用Collections.synchronizedMap(new HashMap<>());

2、使用 ConcurrentHashMap

Map<String, String> map = new ConcurrentHashMap<>();

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

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

相关文章

Spring源码学习:三级缓存的必要性

目录前言概述正文Spring的生命周期Spring中循环依赖场景Spirng中的三级缓存Spring一级缓存解决循环依赖Spring二级缓存解决循环依赖Spring三级缓存解决循环依赖总结前言 工作中可能会碰到循坏依赖问题&#xff0c;所以了解其Spring设计原理&#xff0c;对于解决问题更加高效。…

c++ - 第26节 - c++知识梳理

1.STL知识梳理 STL知识掌握&#xff1a; 底层实现角度&#xff1a;六大组件 上层用的角度&#xff1a;容器、算法、迭代器 底层实现角度&#xff1a; 注&#xff1a; 1.可以认为迭代器是容器和算法的粘合剂&#xff0c;如果没有迭代器&#xff0c;那么算法要访问容器有两大问题…

三种循环的区别

三种循环的区别:1.for循环和while循环先判断条件是否成立&#xff0c;然后决定是否执行循环体&#xff08;先判断后执行)2.do..while循环先执行一次循环体&#xff0c;然后判断条件是否成立&#xff0c;是否继续执行循环体(先执行后判断)for和while的区别:1.条件控制语句所控制…

英语学习打卡day2

2023.1.20 1.if虚拟语气的倒装 If it were not for your help, I would be homeless. Were it not for your help, I would be homeless. 要不是你的帮助&#xff0c;我会无家可归。 2.plausible adj.似乎有理的;有道理的 plaus拍手&#xff0c;鼓掌 ible可…的- >能鼓掌的…

注册中心(一)

注册中心&#xff08;一&#xff09; 业务痛点 项目的架构从以前的单体结构发展到现在的微服务。不仅服务的数量变的多了&#xff0c;而且服务都是多节点的部署。 假如在serviceA去调用serviceB&#xff0c;当serviceA会在配置中配置一个serviceB的ip和port进行通信。 当se…

如何安装配置hbase

当完成hdfs、zookeeper的安装配置后&#xff0c;现在进入到hbase的安装和配置环节。这样的做的目的之一是要把海量的数据存入到hbase数据库中。JDK版本的要求hbase对JDK版本是有要求的&#xff0c;不是JDK版本越高越好&#xff0c;根据我走过的坑&#xff0c;目前最好的JDK版本…

LeetCode刷题复盘笔记—一文搞懂贪心算法之122. 买卖股票的最佳时机 II问题(贪心算法系列第三篇)

今日主要总结一下可以0贪心算法解决的一道题目&#xff0c;122. 买卖股票的最佳时机 II 题目&#xff1a;122. 买卖股票的最佳时机 II Leetcode题目地址 题目描述&#xff1a; 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&am…

深度学习实战 —— LSTM轨迹预测

前言 最近写了一份用LSTM之类的序列模型+SeNet预测轨迹,经过一系列调试效果最后比较理想了。记录下~ 上效果: 一些效果图 训练误差图: 测试误差图: 训练损失: 测试损失: batch平均耗时: 拟合效果:

(day7) 自学Java——面向对象进阶

目录 1.static静态变量 ​编辑 2.继承 ​编辑 3.多态 4.包、final、权限修饰符、代码块 5.抽象类 6.接口 7.内部类 非原创&#xff0c;为方便自己后期复习 1.static静态变量 静态存储位置的数据是共享的 练习&#xff1a;定义数组工具类 需求&#xff1a;在实际开发…

深度卷积对抗神经网络 基础 第二部分 DC-GANs

深度卷积对抗神经网络 基础 第二部分 DC-GANs DC-GANs &#xff08;DC-GANs Deep convolutional GAN&#xff09;是基于GANs的一种专门对图片生成的一种模型&#xff0c;其通过卷积操作来进行图片的一些基本操作来实现模型的功能。 激活函数 Activations 激活函数是任何输入…

代码随想录--哈希表章节总结

代码随想录–哈希表章节总结 1. LeetCode242 有效的字母异位词 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 示例 1: 输入: s &quo…

高德地图红绿灯读秒是怎么实现的?(二)

通过上一篇高德官方回复&#xff0c;以及一些科技大佬们的脑回路&#xff0c;做了一些简单的回复&#xff1b; 这次好好的从个人研究观点来阐述一下这个论题 目前有两种说法&#xff0c;一种说是靠大数据分析&#xff0c;一种说是靠交管部门数据。 从个人的研究来看&#xff0…

socket 2---TCP编程

目录 一、TCP编程流程 二、函数接口 2.1、监听接口 2.2、发起连接 connect 2.3、接收新连接 accept 2.4、收发接口 三、代码实现 问题&#xff1a; 要是创建多个客户端的话会怎么样呢&#xff1f; 那么怎么去真正解决这个问题呢&#xff1f; 一、TCP编程流程 这里…

数据结构与算法基础(王卓)(9):线性表的应用(有序表合并)(有序,可重复)

PPT&#xff1a;第二章P176&#xff1b; 合并为一个新的整体&#xff1a;有序表的合并&#xff08;有序&#xff0c;可重复&#xff09; 线性表&#xff1a; 对于该操作的具体实现的流程设计&#xff1a;&#xff08;各模块&#xff09; 创建一个空表C 依次从A或B(中&#…

移动云国产商用密码规模化应用

前言 为深入贯彻落实《密码法》&#xff0c;推动商用密码技术在工业和信息化行业领域的融合应用&#xff0c;工业和信息化部密码应用研究中心组织开展了“首届全国商用密码应用优秀案例征集”工作&#xff0c;并评审选出15项优秀案例。 同时&#xff0c;为持久发挥本次活动的…

CSAPP笔记

目录 第一章 一个典型的硬件组成 从键盘上读取hello指令​编辑 存储器结构示例 相对性能公式 计算机系统抽象 第二章--信息的表示和处理 按位 & | ^ ~ 与逻辑运算 && || 逻辑右移和算术右移 左移 定义计算机如何编码和操作整数的数学定义 补码编码的定义 补码…

【JavaWeb】前端开发三剑客之CSS(上)

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaWeb】 ✈️✈️本篇内容:CSS从零开始学习&#xff01; &#x1f680;&#x1f680;代码托管平台github&#xff1a;JavaWeb代码存放仓库&#xff01; ⛵⛵作…

擎创动态 | 官宣!与深智城集团正式签约

近日&#xff0c;上海擎创信息技术有限公司与深圳市智慧城市科技发展集团有限公司&#xff08;以下简称“深智城集团”&#xff09;就“一体化协同办公平台项目”达成战略合作&#xff0c;签约仪式已圆满完成。 ​深智城集团副总经理罗介平、智城软件公司常务副总经理韩小宇、智…

android 读取assets配置文件

方法1-getAssets().open(“re.properties”) try {Properties props new Properties();props.load(getAssets().open("re.properties"));Log.e(TAG, "className:" props.getProperty("className"));} catch (IOException e) {e.printStackTrace…

支持加密的日记应用程序DailyTxT

本文完成于 12 月下旬&#xff0c;对应的版本为 1.0.10(2022_11_02)&#xff1b; 什么是 DailyTxT &#xff1f; DailyTxT 是一个加密的 Web 日记应用程序&#xff0c;用于写下您当天的故事并轻松地再次找到它们。它是用 Python Flask&#xff08;后端&#xff09;和 Vue.JS&am…