并发编程修炼八:Java ConcurrentModificationException异常原因和解决方法

Java核心知识体系深度解析:从JVM原理到高并发实战 Java作为企业级应用开发的主流语言,其核心知识体系是每位开发者必须掌握的基础。理解JVM内存模型是掌握Java程序运行原理的关键,它定义了线程与主内存间的交互规则,直接影响程序的并发安全与性能表现。在此基础上,集合框架作为数据结构的集大成者,其设计思想与实现机制(如HashMap的负载因子与红黑树转换)深刻体现了数据结构与算法在工程实践中的权衡。深入理解这些原理,不仅能优化代码性能,更能为应对高并发场景(如ConcurrentHashMap的锁优化)和复杂系统设计打下坚实基础。本文聚焦于Java核心脉络的 阅读详情

作者:海子
出处:http://www.cnblogs.com/dolphin0520/

目录:

一.ConcurrentModificationException异常出现的原因

二.在单线程环境下的解决办法

三.在多线程环境下的解决方法

一.ConcurrentModificationException异常出现的原因

  先看下面这段代码:
  

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        }
    }
}

  运行结果:
  这里写图片描述

从异常信息可以发现,异常出现在checkForComodification()方法中。

  我们不忙看checkForComodification()方法的具体实现,我们先根据程序的代码一步一步看ArrayList源码的实现:

  首先看ArrayList的iterator()方法的具体实现,查看源码发现在ArrayList的源码中并没有iterator()这个方法,那么很显然这个方法应该是其父类或者实现的接口中的方法,我们在其父类AbstractList中找到了iterator()方法的具体实现,下面是其实现代码:

public Iterator<E> iterator() {
    return new Itr();
}

从这段代码可以看出返回的是一个指向Itr类型对象的引用,我们接着看Itr的具体实现,在AbstractList类中找到了Itr类的具体实现,它是AbstractList的一个成员内部类,下面这段代码是Itr类的所有实现:

private class Itr implements Iterator<E> {
    int cursor = 0;
    int lastRet = -1;
    int expectedModCount = modCount;
    public boolean hasNext() {
           return cursor != size();
    }
    public E next() {
           checkForComodification();
        try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
    }
    public void remove() {
        if (lastRet == -1)
        throw new IllegalStateException();
           checkForComodification();

        try {
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
            cursor--;
        lastRet = -1;
        expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
}

首先我们看一下它的几个成员变量:

  cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出

  lastRet:表示上一个访问的元素的索引

  expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。

  modCount是AbstractList类中的一个成员变量

protected transient int modCount = 0;

该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。

  好了,到这里我们再看看上面的程序:

  当调用list.iterator()返回一个Iterator之后,通过Iterator的hashNext()方法判断是否还有元素未被访问,我们看一下hasNext()方法,hashNext()方法的实现很简单:

public boolean hasNext() {
    return cursor != size();
}

如果下一个访问的元素下标不等于ArrayList的大小,就表示有元素需要访问,这个很容易理解,如果下一个访问元素的下标等于ArrayList的大小,则肯定到达末尾了。

  然后通过Iterator的next()方法获取到下标为0的元素,我们看一下next()方法的具体实现:

public E next() {
    checkForComodification();
 try {
    E next = get(cursor);
    lastRet = cursor++;
    return next;
 } catch (IndexOutOfBoundsException e) {
    checkForComodification();
    throw new NoSuchElementException();
 }
}

这里是非常关键的地方:首先在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。

  接着往下看,程序中判断当前元素的值是否为2,若为2,则调用list.remove()方法来删除该元素。

  我们看一下在ArrayList中的remove()方法做了什么:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}


private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    elementData[--size] = null; // Let gc do its work
}

通过remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。

  那么注意此时各个变量的值:对于iterator,其expectedModCount为0,cursor的值为1,lastRet的值为0。

  对于list,其modCount为1,size为0。

  接着看程序代码,执行完删除操作后,继续while循环,调用hasNext方法()判断,由于此时cursor为1,而size为0,那么返回true,所以继续执行while循环,然后继续调用iterator的next()方法:

  注意,此时要注意next()方法中的第一句:checkForComodification()。

  在checkForComodification方法中进行的操作是:

final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。

  很显然,此时modCount为1,而expectedModCount为0,因此程序就抛出了ConcurrentModificationException异常。

  到这里,想必大家应该明白为何上述代码会抛出ConcurrentModificationException异常了。

关键点:
调用list.remove()方法导致modCount和expectedModCount的值不一致
注意,像使用for-each进行迭代实际上也会出现这种问题

二.在单线程环境下的解决办法

既然知道原因了,那么如何解决呢?

  其实很简单,细心的朋友可能发现在Itr类中也给出了一个remove()方法:

public void remove() {
    if (lastRet == -1)
    throw new IllegalStateException();
       checkForComodification();

    try {
    AbstractList.this.remove(lastRet);
    if (lastRet < cursor)
        cursor--;
    lastRet = -1;
    expectedModCount = modCount;
    } catch (IndexOutOfBoundsException e) {
    throw new ConcurrentModificationException();
    }
}

 在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作:

expectedModCount = modCount;

因此,在迭代器中如果要删除元素的话,需要调用Itr类的remove方法。

  将上述代码改为下面这样就不会报错了:

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                iterator.remove();   //注意这个地方
        }
    }
}

三.在多线程环境下的解决方法

上面的解决办法在单线程环境下适用,但是在多线程下适用吗?看下面一个例子:

public class Test {
    static ArrayList<Integer> list = new ArrayList<Integer>();
    public static void main(String[] args)  {
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Thread thread1 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    System.out.println(integer);
                }
            };
        };
        Thread thread2 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    if(integer==2)
                        iterator.remove(); 
                }
            };
        };
        thread1.start();
        thread2.start();
    }
}

 运行结果:

这里写图片描述

有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。

原因在于,虽然Vector的方法采用了synchronized进行了同步,但是由于Vector是继承的AbstarctList,因此通过Iterator来访问容器的话,事实上是不需要获取锁就可以访问。那么显然,由于使用iterator对容器进行访问不需要获取锁,在多线程中就会造成当一个线程删除了元素,由于modCount是AbstarctList的成员变量,因此可能会导致在其他线程中modCount和expectedModCount值不等。

就比如上面的代码中,很显然iterator是线程私有的, 

初始时,线程1和线程2中的modCount、expectedModCount都为0,

当线程2通过iterator.remove()删除元素时,会修改modCount值为1,并且会修改线程2中的expectedModCount的值为1,

而此时线程1中的expectedModCount值为0,虽然modCount不是volatile变量,不保证线程1一定看得到线程2修改后的modCount的值,但是也有可能看得到线程2对modCount的修改,这样就有可能导致线程1中比较expectedModCount和modCount不等,而抛出异常。

因此一般有2种解决办法:

  1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;

  2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

public class test {
    static ArrayList<Integer> list = new ArrayList<Integer>();
    public static void main(String[] args)  {
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Thread thread1 = new Thread(){
            public void run() {
                //Iterator<Integer> iterator = list.iterator();
                Iterator<Integer> iterator = remove(list);
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    System.out.println(integer);

                }
            };
        };
        Thread thread2 = new Thread(){
            public void run() {
               // Iterator<Integer> iterator = list.iterator();
                Iterator<Integer> iterator = remove(list);
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    if(integer==2)
                        iterator.remove();
                }
            };
        };
        thread1.start();
        thread2.start();
    }

    public static synchronized Iterator remove(ArrayList list){
        Iterator<Integer> iterator = list.iterator();
        return iterator;
    }
}
Java学习路线与核心知识点全解析 Java作为面向对象编程语言的代表,其平台无关性和健壮的内存管理机制使其成为企业级开发的首选。JVM通过字节码转换实现跨平台运行,而垃圾回收机制则自动管理内存生命周期,这些特性大幅降低了开发复杂度。在分布式系统和高并发场景中,Java的集合框架与多线程模型展现出强大优势,如ConcurrentHashMap解决HashMap的线程安全问题。掌握Java核心原理不仅能优化应用性能,如通过调整JVM参数预防OOM错误,更是学习Spring等流行框架的基础。本文系统梳理从基础语法到JVM调优的全链路知识体系,帮助 阅读详情

相关推荐

程序员成长契约:从编码到技术主权的三层跃迁

在软件工程实践中,‘程序员’不仅是写代码的角色,更是系统可靠性、业务可维护性与技术决策伦理的共同承担者。理解.NET运行时底层机制(如async/await状态机、ThreadPool调度、GC行为)是构建高可信度系统的前提;掌握源码级溯源能力与跨栈穿透分析方法,则能突破框架黑盒依赖,实现真正可控的技术选型。这种深度能力支撑起‘技术主权’——即对系统行为可解释、可验证、可演进的掌控力。它广泛应用于金融交易、政务云、电商秒杀等强一致性与高可用场景,并为中级开发者提供从‘能干活’到‘懂设计、敢决策’的职业升级路

diaoqi6581的博客 539

Java ConcurrentModificationException异常分析

项目中有一个需求: 服务器在检测到设备接入后给设备下发配置。在服务器异常重启的场景下,服务器检测到终端建链时通信模块还没有初始化完成,此时需要将已经建链的设备的建链通知消息缓存起来,创建线程,在县城中待通信模块初始化完成后给设备下发配置。因频繁涉及节点增删,这里用LinkedList缓存触发建链通知的设备。(由于服务器需要支持10万台设备的并发容量,当服务器重启后,短时间内会有大量设备建链并触发

做好自己 832

从Pikachu靶场实战解析越权漏洞:原理、攻击与防御

本文通过Pikachu靶场实战详细解析越权漏洞的原理、攻击方式与防御策略。文章深入探讨水平越权和垂直越权的危害,结合代码示例展示漏洞利用过程,并提供代码层和架构层的立体化防御方案,帮助开发者有效防范这一Web安全隐形杀手。

weixin_29325007的博客 358

java.util.ConcurrentModificationException 解决方案

ArrayList线程不安全

weixin_39709686的博客 1944

±10V ADC 输入电路的高共模差分输入电压

这篇文章分析了±10V ADC输入电路的设计需求与挑战,重点讨论了高共模差分输入电压的处理方案。文章首先明确了设计目标,即安全转换±10V差分信号并保持信号完整性,同时克服高共模电压、阻抗匹配和噪声优化等难点。随后详细比较了仪表放大器和差分放大器的性能特点,并提出了器件选型理论。在信号链架构部分,文章通过公式推导阐述了增益设置、共模抑制设计和输入阻抗匹配等关键技术。此外,还建立了噪声源模型,提出了噪声优化措施。最后,文章总结了设计验证方法,并扩展了在工业保护和生物电信号采集等场景的应用。该设计强调根据具体应

泛红尘的博客 1959

手把手教你用思岚RPLIDAR C1激光雷达实现简单环境扫描(树莓派4B+Ubuntu22.04)

本文详细介绍了如何在树莓派4B(Ubuntu 22.04系统)上驱动思岚RPLIDAR C1激光雷达,从硬件连接、权限配置、SDK编译到运行示例程序,最终实现环境扫描数据的可视化。通过手把手教程,帮助开发者和爱好者快速上手激光雷达应用,构建简单的环境扫描图。

j2k3l4的博客 1132

java.util.ConcurrentModificationException异常原因及解决方法

通过迭代器循环通知消息,foreach直接循环会报java.util.ConcurrentModificationException异常。

行者-崛起之路 2980

FunASR语音识别服务在Win11上的Docker部署避坑指南:模型配置与热词优化

本文详细介绍了在Windows 11系统上通过Docker部署FunASR语音识别服务的完整流程,包括环境准备、模型配置、热词优化及常见问题解决方案。特别针对Win11环境下的特殊配置和性能调优提供了实用指南,帮助开发者高效搭建高性能语音识别服务。

e3f4g5的博客 222

一文搞懂java.util.ConcurrentModificationException异常原因和解决方法

文章目录场景复现原因分析解决方法ListMap 场景复现 对于list,map都有可能出现,这里主要分析list! @Test public void testList() { List<String> list = new ArrayList(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add

Apple_Boy的博客 1万+

ComfyUI Impact Pack实战:如何用三大检测器打造高效人脸修复工作流(附2025最新模板)

本文详细解析了如何利用ComfyUI Impact Pack中的BBOX、Segm和SAM三大检测器构建高效人脸修复工作流。通过技术选型、参数优化和实战案例,帮助用户掌握从精准定位到细节修复的全流程技巧,特别适用于商业摄影和影视制作场景。文章还提供了2025年最新模板和性能优化方案,显著提升人脸精修效率。

weixin_29040367的博客 245

【系统架构设计师-2026上半年真题】案例分析-参考答案及详解(回忆版)

架构评估与设计:通过入侵检测系统的案例分析,阐述质量属性(性能/可用性/安全性/可修改性)的匹配方法,推荐管道-过滤器风格处理实时数据流,并设计8个核心构件实现数据采集到告警的全流程处理。 高并发优化方案: 分析悲观锁(排他锁/共享锁)与乐观锁的特性差异,提出乐观锁通过版本号/CAS实现,悲观锁适用于强一致性场景。 给出Redis优化三手段:分布式锁控制临界资源、Lua脚本保证原子操作、热点数据多级缓存,结合消息队列削峰填谷。 分层架构实践: 智

数据知道的博客 1681

多线程与线程池技术详解

在当前的Java面试和后台开发中,多线程与线程池技术越来越重要。毫不夸张的说,如何你想进入任何一家好的网络公司并能够长足发展,多线程与线程池技术是必须要掌握的技能!

2025年GESP3月认证C++五级真题解析

小 A 有 2n 件物品,小 B 和小 C 想从小 A 手上买走这些物品。对于第 i 件物品,小 B 会以 bi 的价格购买,而小 C 会以 ci 的价格购买。对于所有测试点,保证 1 <= T <= 100, 2 <= g < p <= 10^9, g, p 为整数, p 为质数。对于所有测试点,保证 1 <= n <= 10^5, 0 <= bi, ci <= 10^9, bi, ci 为整数。对于另外 50 % 的测试点,保证 n <= 100000, 0 <= bi, ci <= 10^9。

lan_in的博客 1035

Java核心基础:从JVM内存模型到并发编程的实战内功修炼

Java作为企业级应用的主流语言,其核心基础是构建稳健、高性能系统的基石。理解Java内存模型(JMM)是掌握并发编程的关键,它定义了线程间共享变量的访问规则,解决了可见性、原子性和有序性问题。通过深入分析JVM内存区域划分,如堆、方法区(Metaspace),开发者能够有效诊断和解决`OutOfMemoryError`等线上疑难杂症,这是性能调优与系统稳定的技术保障。在并发场景下,从`synchronized`锁的升级优化机制到`java.util.concurrent`工具包的无锁CAS实现,这些原理为

weixin_30399155的博客 395

NVIDIA AI 学习与认证指南:从入门到精通

NVIDIA的AI学习与认证体系,凭借其先进的技术背景、丰富的课程资源以及强大的行业认可度,已成为AI学习者和从业者的宝贵资源。通过深入学习NVIDIA的课程和获得认证,学习者可以快速掌握最前沿的AI技术,提升自己的职业竞争力。从基础课程到专业认证,NVIDIA为不同层次的学习者提供了量身定制的学习资源,帮助他们掌握AI的核心技能,提升职业竞争力。是其中的一个亮点,它结合了信息检索与生成式AI的能力,使AI可以从外部知识库中实时提取信息,生成更具深度和准确性的内容。,帮助学习者快速掌握该领域的核心技术。

高校图书馆高级系统架构师/研究馆员,推动高校AI从概念演示到可评审、可交付、可运维:路线图与立项评审、PoC交付、评测回归与上线运维(Baseline/Eval/Ops)。 1879

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

迭代大师的修炼之道:Java中Iterator与Enhanced for loop的深度解析

在Java编程的广阔天地中,迭代就像是开启宝藏箱的钥匙,而Iterator与Enhanced for loop(增强型for循环)则是这把钥匙的两种形态,它们各有千秋,引领我们探索数据结构的奥秘。今天,我们将深入剖析这两种迭代技术,从理论到实践,从基础到进阶,带你领略迭代的魅力。

qq_44771627的博客 586

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Java学习路线与核心语法精要解析

Java作为一门广泛应用于企业级开发的编程语言,其核心语法和面向对象编程(OOP)原理是开发者必须掌握的基础。OOP通过封装、继承和多态三大特性,实现了代码的模块化和复用,而Java的集合框架和异常处理机制则是日常开发中的高频使用技术。理解这些基础概念后,开发者可以更高效地使用Spring等流行框架,并应对并发编程、JVM调优等进阶挑战。本文基于Java 11 LTS版本,详细解析环境配置、OOP深层原理以及集合框架的选用策略,帮助开发者构建扎实的Java基础。

weixin_30615767的博客 406

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Iterator 与 fail-fast 机制:你不知道的细节

它的迭代器遍历的是创建迭代器时的快照,所以遍历过程中其他线程的修改不会影响当前遍历,也不会抛 ConcurrentModificationException。先用 iterator 找到要删除的元素,然后调用 iterator.remove(),这样 modCount 和 expectedModCount 会保持同步,不会触发 fail-fast。第二,如果要按条件删除多个元素,强烈推荐使用 removeIf() 方法,这是 JDK 8 引入的,底层帮你处理了 Iterator 的正确使用方式。

hello world! 59

Java面试八股文全攻略:集合、并发与JVM高频考点深度剖析

在Java后端开发的知识体系中,基础概念的掌握程度往往决定了技术面试的上限。无论是集合框架中的HashMap底层结构,还是并发编程中volatile与synchronized的语义,亦或是JVM的内存模型与垃圾回收机制,这些核心知识点不仅是面试官考察的焦点,更是实际工程中排查问题、优化性能的理论基石。理解其设计原理与适用场景,能够帮助开发者从“会用”走向“懂为什么”,在应对复杂业务时做出更合理的技术选型。对于准备Java面试的开发者而言,系统梳理这些高频考点,并建立从概念到源码的认知路径,是提升竞争力的有效

weixin_30516429的博客 250

数据结构习题三层价值:从算法思维到工程实战的进阶指南

数据结构是计算机科学的核心基础,它研究数据的组织、存储和操作方式,直接影响算法的效率与程序的性能。其核心原理在于通过抽象建模,将现实问题映射为可计算的数据关系,并利用特定结构的特性(如数组的随机访问、链表的动态增删、哈希表的快速查找)来优化操作。掌握数据结构的技术价值在于培养“将抽象问题映射到具体数据模型,并利用模型特性设计高效算法”的底层思维能力,这是区分代码实现者与问题解决者的关键。在实际应用场景中,这种能力不仅用于解决算法面试题,更贯穿于系统设计、性能优化和并发编程等工程实践。例如,在实现**LRU缓

weixin_34268579的博客 354

LinkedBlockingQueue操作,线程安全问题,ConcurrentModificationException 异常分析与解决方案...

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33995481的博客 638

Java面试进阶:并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?

答: Concurrent 类型基于 lock-free,基于CAS的无锁技术,在常见的多线程访问场景,一般可以提供较高吞吐量。 而 LinkedBlockingQueue 内部则是基于锁,并提供了 BlockingQueue 的等待性方法。 ,java.util.concurrent 包提供的容器 大概区分为 Concurrent*、CopyOnWrite和 Blocking等三类...

qililong88的博客 719

ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?

典型回答 有时候我们把并发包下面的所有容器都习惯叫作并发容器,但是严格来讲,只有以“Concurrent”为前缀的容器才是真正的并发容器。 ConcurrentLinkedQueue基于lock-free,在常见的多线程访问场景,一般可以提供较高吞吐量。 而LinkedBlockingQueue内部则是基于锁,并提供了BlockingQueue的等待性方法。 不知道你有没有注意到,java....

qweqwruio的博客 3059

队列+线程池消费导致ConcurrentModificationException

@Autowired PieceSearchDao pieceSearchDao; public void getInfoFromIntopieces() { try{ List&lt;IntopieceByEagleDTO&gt; list = pieceSearchDao.queryForInsert(); E...

Ops的博客 425
上一篇: 并发编程修炼七:同步容器
下一篇: 并发编程修九:并发容器之CopyOnWriteArrayList
yjgithub
博客等级 码龄11年 53粉丝 105原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值