Java面试——不安全的集合类

news2024/12/28 5:14:23

系统性学习,移步IT-BLOG-CN

Java 中有许多的集合,常用的有List,Set,Queue,Map。 其中 List,Set,Queue都是Collection(集合),List中<>的内容表示其中元素的类型,是泛型的一种使用。不能直接使用简单数据类型做泛型的原因:集合类(比如Set)在进行各种 “操作” ( 如contains()) 时都会调用元素本身提供的 “方法” ( 如hashCode() 和 equals()),而不是由集合类自身去实现这些 “方法”。这就要求如果某人想要用这个集合执行某些 “操作”,那就必须在要加入集合的元素中实现相应的 “方法”。

fail-fast 机制是 java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast事件。例如:当某一个线程A通过 iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生 fail-fast事件。

一、ArrayList 不安全阐述

List(列表)相当于数组。长度可变,元素存放有一定的顺序,下标从0开始。在JDK中,List作为接口,本身已经声明好了所有的方法(比如add(), contains()…),所以不管是选择 ArrayList还是 LinkedList,完成各种操作的时候依然是使用 List中已经声明过的这一套方法,对使用者来说没有区别。二者只是内部实现逻辑不同,所以在不同的应用场景下会有不同的效率。

【1】ArrayList<> 底层通过数组实现数据的存储。初始的大小为10,超过默认值时,通过 Arrays 进行扩容,如下:允许存空元素,有专门保存容量的 capacity属性

//elementData 需要扩容的数组对象 , newCapacity 扩容的大小 int 类型
elementData = Arrays.copyOf(elementData, newCapacity);

【2】ArrayList 线程不安全的例子如下:

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i=1;i<3000;i++){
            new Thread(){
                @Override
                public void run(){
                    list.add(UUID.randomUUID().toString().substring(0,7));
                    System.out.println(list);
                }
            }.start();
        }
    }
}

【3】故障现象: java.util.ConcurrentModificationException
在这里插入图片描述
【4】导致原因: 因并发无锁导致数据修改异常。
【5】解决方案: ① Vector 是线程安全的,可以解决上面的问题。但是性能会急剧下降(不建议使用)。
② 使用Collections工具类 Collections.synchronizedList(new ArrayList<>()); 解决上述问题。
③ new CopyOnWriteArrayList<>():写时复制,CopeOnWrite 容器既写时复制的容器。往一个容器添加元素的时候,不直接往当前容器 Object[] 添加,而是先将当前容器 object[] 进行 copy,复制出一个新的容器 Object[] newElements,然后往新的容器中添加元素,添加完元素之后,再将原容器的引用指向新容器 setArray(newElements); 这样做的好处是可以对 CopeOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopeOnWrite 容器也是一种读写分离的思想,读和写不同的容器。底层源码如下:

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

扩展一:Arrays.asList 遇到的问题

使用Arrays.asList()方法时把一个数组转化成 List列表,对得到的 List列表进行 add()和 remove()操作, 会导致 java.lang.UnsupportedOperationException异常。

【1】查看 Arrays.asList 源码

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

【2】查看此 ArrayList结构:add 和 remove 方法继承自 AbstractList

private static class ArrayList<E> extends AbstractList<E> {
        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
}

【3】在查看 AbstractList结构:add 和 remove 方法直接返回 UnSupportedOperationException

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    public boolean add(E e) {
        add(size(), e);
        return true;
    }

    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

   public E remove(int index) {
        throw new UnsupportedOperationException();
    }
}

所以说 Arrays.asList 返回的 List 是一个不可变长度的列表,此列表不再具备原 List 的很多特性,因此慎用 Arrays.asList 方法。

下面代码输出是什么?

public static void main(String[] args) {
    int[] data = {1,2,3,4};
    List list = Arrays.asList(data);
    System.out.println(list.size());
}

由上面 asList 源码我们可以看到返回的 Arrays的内部类 ArrayList 构造方法接收的是一个类型为 T 的数组,而基本类型是不能作为泛型参数的,所以这里参数 a只能接收引用类型,自然为了编译通过编译器就把上面的 int[] 数组当做了一个引用参数,所以 size 为 1,要想修改这个问题很简单,将 int[] 换成 Integer[] 即可。所以原始类型不能作为 Arrays.asList 方法的参数,否则会被当做一个参数。

扩展二:ArrayList 的 subList的注意事项

《阿里巴巴Java开发手册》泰山版中是这样描述的:
在这里插入图片描述

使用起来很简单,也很好理解,不过还是有以下几点要注意,否则会造成程序错误或者异常:
【1】修改原集合元素的值,会影响子集合;
【2】修改原集合的结构,会引起 ConcurrentModificationException异常;
在这里插入图片描述

看下它的源码:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

可以看到,它调用了 SubList类的构造函数,该构造函数的源码如下图所示:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
        ......
    private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

        SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }
......

可以看出,SubList类是 ArrayList的内部类,该构造函数中也并没有重新创建一个新的 ArrayList,所以修改原集合或者子集合的元素的值,是会相互影响的。

二、Set 不安全阐述

Set 与 List 是相同的,都是线程不安全的,都会出现 ConcurrentModificationException 异常,解决办法常见的有两种:
【1】Collections.synchronizedSet(new HashSet<>()); 通过工具类中的同步代码块可以解决此问题,但性能会受影响。
【2】new CopyOnWriteArraySet<>() 和 List 相同,通过写时复制即可高效解决此问题。底层通过 CopyOnWriteArrayList 实现:

public CopyOnWriteArraySet() {
    al = new CopyOnWriteArrayList<E>();
}

扩展:hashSet 的底层是怎么实现的:底层其实是一个 hashMap,源代码如下:

public HashSet() {
    map = new HashMap<>();
}

我们的疑惑是,Map 不应该存放的是两个值么,而 Set 存储的都是一个值呀,其实是因为 Map 中的 Key 与 Set 具有相同的特性。因此 Set 的值都存储在 Map 中的 key 中,而 value 存储一个固定的 Object 常量。源代码如下:

//存储的 value 值
private static final Object PRESENT = new Object();
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}

三、Map 不安全阐述

Map 与 List/Set 是相同的,都是线程不安全的,都会出现 ConcurrentModificationException 异常,解决办法常见的有三种:
【1】Collections.synchronizedMap(new HashMap<>()); 通过工具类中的同步代码块可以解决此问题,但性能会受影响。
【2】HashTable: HashTable 容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。
【3】new ConcurrentHashMap<>(); 推荐使用,此方法创建的 Map 是线程安全的。而 JDK1.7 之前的 ConcurrentHashMap 使用分段锁机制实现,JDK1.8 则使用数组+链表+红黑树数据结构和CAS原子操作实现 ConcurrentHashMap;
具体可以查看:链接

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

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

相关文章

Linux CPU 飙升 排查五步法

排查思路-五步法 1. top命令定位应用进程pid 找到最耗时的CPU的进程pid top2. top-Hp[pid]定位应用进程对应的线程tid 找到最消耗CPU的线程ID // 执行 top -Hp [pid] 定位应用进程对应的线程 tid // 按shift p 组合键&#xff0c;按照CPU占用率排序 > top -Hp 111683.…

华为手机ip地址怎么切换

随着移动互联网的普及&#xff0c;IP地址成为了我们手机上网的重要标识。然而&#xff0c;在某些情况下&#xff0c;我们可能需要切换手机的IP地址&#xff0c;以更好地保护个人隐私、访问特定地区的内容或服务&#xff0c;或者出于其他网络需求。华为手机作为市场上的热门品牌…

【uniapp】H5+、APP模拟浏览器环境内部打开网页

前言 今天将智能体嵌入到我的项目中&#xff0c;当作app应用时&#xff0c;发现我使用的webview组件&#xff0c;无论H5怎么登录都是未登录&#xff0c;而APP却可以&#xff0c;于是进行了测试&#xff0c;发现以下几种情况&#xff1a; 方法<a>标签webviewAPP✅✅网页…

Spring扩展点(一)Bean生命周期扩展点

Bean生命周期扩展点 影响多个Bean的实例化InstantiationAwareBeanPostProcessorBeanPostProcessor 影响单个Bean的实例化纯粹的生命周期回调函数InitializingBean&#xff08;BeanPostProcessor 的before和after之间调用&#xff09;DisposableBean Aware接口在生命周期实例化过…

Hive大数据任务调度和业务介绍

目录 一、Zookeeper 1.zookeeper介绍 2.数据模型 3.操作使用 4.运行机制 5.一致性 二、Dolphinscheduler 1.Dolphinscheduler介绍 架构 2.架构说明 该服务内主要包含: 该服务包含&#xff1a; 3.FinalShell主虚拟机启动服务 4.Web网页登录 5.使用 5-1 安全中心…

[入门] Unity Shader前置知识(5) —— 向量的运算

在Unity中&#xff0c;向量无处不在&#xff0c;我想很多人都使用过向量类的内置方法 normalized() 吧&#xff0c;我们都知道该方法是将其向量归一化从而作为一个方向与速度相乘&#xff0c;以达到角色朝任一方向移动时速度都相等的效果&#xff0c;但内部具体是如何将该向量进…

【计算机科学速成课】笔记二

笔记一 文章目录 7.CPU阶段一&#xff1a;取指令阶段阶段二&#xff1a;解码阶段阶段三&#xff1a;执行阶段 8.指令和程序9.高级CPU设计——流水线与缓存 7.CPU CPU也叫中央处理器&#xff0c;下面我们要用ALU&#xff08;输入二进制&#xff0c;会执行计算&#xff09;、两种…

STM32之HAL开发——ADC入门介绍

ADC简介 模数转换&#xff0c;即Analog-to-Digital Converter&#xff0c;常称ADC&#xff0c;是指将连续变量的模拟信号转换为离散的数字信号的器件&#xff0c;比如将模温度感器产生的电信号转为控制芯片能处理的数字信号0101&#xff0c;这样ADC就建立了模拟世界的传感器和…

C++异常处理实现(libstdc++)

摘要&#xff1a;为了更好的理解C中异常处理的实现&#xff0c;本文简单描述了Itanium ABI中异常处理的流程和llvm/libsdc简要实现。 关键字&#xff1a;C,exception,llvm,clang C他提供了异常处理机制来对程序中的错误进行处理&#xff0c;避免在一些异常情况下无法恢复现场而…

Android C++ 开发调试 LLDB 工具的使用

文章目录 调试环境准备基础命令Breakpoint CommandsWatchpoint CommandsExamining VariablesEvaluating ExpressionsExamining Thread StateExecutable and Shared Library Query Commands 参考&#xff1a; Android 中在进行 NDK 开发的时候&#xff0c;我们经常需要进行 C 代…

漏洞挖掘之某厂商OAuth2.0认证缺陷

0x00 前言 文章中的项目地址统一修改为: a.test.com 保护厂商也保护自己 0x01 OAuth2.0 经常出现的地方 1&#xff1a;网站登录处 2&#xff1a;社交帐号绑定处 0x02 某厂商绑定微博请求包 0x02.1 请求包1&#xff1a; Request: GET https://www.a.test.com/users/auth/weibo?…

88、动态规划-乘积最大子数组

思路&#xff1a; 首先使用递归来解&#xff0c;从0开始到N&#xff0c;每次都从index开始到N的求出最大值。然后再次递归index1到N的最大值&#xff0c;再求max。代码如下&#xff1a; // 方法一&#xff1a;使用递归方式找出最大乘积public static int maxProduct(int[] num…

局部性原理和磁盘预读

局部性原理 磁盘预读 \

Linux---软硬链接

软链接 我们先学习一下怎样创建软链接文件&#xff0c;指令格式为&#xff1a;ln -s 被链接的文件 生成的链接文件名 我们可以这样记忆&#xff1a;ln是link的简称&#xff0c;s是soft的简称。 我们在下面的图片中就是给test文件生成了一个软链接mytest&#xff1a; 我们来解…

【Linux—进程间通信】共享内存的原理、创建及使用

什么是共享内存 共享内存是一种计算机编程中的技术&#xff0c;它允许多个进程访问同一块内存区域&#xff0c;以此作为进程间通信&#xff08;IPC, Inter-Process Communication&#xff09;的一种方式。这种方式相对于管道、套接字等通信手段&#xff0c;具有更高的效率&…

【skill】onedrive的烦人问题

Onedrive的迷惑行为 安装Onedrive&#xff0c;如果勾选了同步&#xff0c;会默认把当前用户的数个文件夹&#xff08;桌面、文档、图片、下载 等等&#xff09;移动到安装时提示的那个文件夹 查看其中的一个文件的路径&#xff1a; 这样一整&#xff0c;原来的文件收到严重影…

孪生网络、匹配网络和原型网络:详解与区分

孪生网络、匹配网络和原型网络 孪生网络、匹配网络和原型网络&#xff1a;详解与区分孪生网络&#xff08;Siamese Networks&#xff09;核心概念工作原理 匹配网络&#xff08;Matching Networks&#xff09;核心概念工作原理 原型网络&#xff08;Prototypical Networks&…

环形链表知识点

目录 判断链表中是否有环快慢指针步数问题 判断链表中是否有环 题目&#xff1a;给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 解决方法&#xff1a;使用快慢指针 如果两个快慢指针相遇&#xff0c;则有环。 如果没有相遇&#xff0c;则没有环。 但是这个原理…

Linux——守护进程化(独立于用户会话的进程)

目录 前言 一、进程组ID与会话ID 二、setsid() 创建新会话 三、daemon 守护进程 前言 在之前&#xff0c;我们学习过socket编程中的udp通信与tcp通信&#xff0c;但是当时我们服务器启动的时候&#xff0c;都是以前台进程的方式启动的&#xff0c;这样很不优雅&#xff0c…

【LinuxC语言】setitimer与getitimer函数

文章目录 前言一、setitimer() 函数二、getitimer() 函数三、示例代码总结 前言 在Linux系统下&#xff0c;编写程序时经常需要使用定时器来实现一些定时任务、超时处理等功能。setitimer() 和 getitimer() 函数是两个用于操作定时器的重要函数。它们可以帮助我们设置定时器的…