当前位置: 首页 > news >正文

Java ConcurrentModificationException 深度剖析开发调试日志

请在此添加图片描述

前言

在Java多线程编程中,ConcurrentModificationException是一个常见的异常,它不仅出现在多线程环境,也会在单线程环境中出现。本文将深入分析这个异常的产生原因、触发条件,并提供多种解决方案及其性能对比,帮助开发者在实际项目中做出最佳选择。

目录

  1. 异常概述
  2. 单线程环境下的异常分析
  3. 多线程环境下的异常分析
  4. 解决方案对比
  5. CopiedIterator实现与分析
  6. 高级解决方案
  7. 性能测试与对比
  8. 异常处理机制深入分析
  9. 实际应用建议
  10. 最佳实践总结
  11. 参考资料

异常概述

ConcurrentModificationException是Java集合框架中的一个运行时异常,它在以下情况下会被抛出:

  • 当一个线程正在迭代集合,而另一个线程同时修改了该集合的结构(添加、删除元素)
  • 当在单线程环境中,使用迭代器遍历集合的同时,通过集合自身的方法修改集合结构

这个异常是Java集合框架的一种**快速失败(fail-fast)**机制,用于检测并发修改,防止程序在不确定状态下继续执行。

在我们的实际测试中,我们发现即使在单线程环境下,如果在遍历过程中直接修改集合,也会抛出此异常。例如:

List<String> fruits = new ArrayList<>();
fruits.add("香蕉");
fruits.add("西瓜");try {for (String fruit : fruits) {if (fruit.equals("香蕉")) {fruits.remove(fruit); // 这里会抛出ConcurrentModificationException}}
} catch (ConcurrentModificationException e) {System.out.println("异常信息: " + e.getMessage());
}

单线程环境下的异常分析

异常复现

在单线程环境下,以下代码会触发ConcurrentModificationException

List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");// 使用for-each循环(底层使用Iterator)
for (String item : list) {if ("item2".equals(item)) {list.remove(item); // 这里会抛出ConcurrentModificationException}
}

源码分析

为什么会抛出这个异常?让我们看看ArrayList的Iterator实现:

  1. 当创建Iterator时,会记录当前集合的modCount值(修改计数器)到expectedModCount
  2. 每次调用next()方法时,会检查modCount是否等于expectedModCount
  3. 如果不相等,说明集合在迭代过程中被修改,立即抛出ConcurrentModificationException

关键源码(简化版):

private class Itr implements Iterator<E> {int expectedModCount = modCount;public E next() {checkForComodification();// ...}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

在我们的测试中,我们还发现不仅List集合会出现这个问题,Map集合同样存在类似问题:

Map<String, String> caches = new HashMap<>();
caches.put("user@getAge@123@v1", "30");
caches.put("user@getAddress@456@v1", "New York");
String sameKeyPart = "user@get";try {Iterator<String> keys = caches.keySet().iterator();while (keys.hasNext()) {String key = keys.next();System.out.println("当前键: " + key);if (key.startsWith(sameKeyPart)) {caches.remove(key); // 这里会抛出ConcurrentModificationException}}
} catch (ConcurrentModificationException e) {System.out.println("捕获异常: " + e.getClass().getName());
}

正确解决方法

  1. 使用Iterator的remove方法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {String item = iterator.next();if ("item2".equals(item)) {iterator.remove(); // 正确的方式}
}

在我们的测试代码中,我们验证了这种方法的有效性:

Map<String, String> caches = new HashMap<>();
caches.put("user@getName@123@v1", "John");
caches.put("user@getEmail@123@v1", "john@example.com");
String sameKeyPart = "user@get";Iterator<String> keys = caches.keySet().iterator();
while (keys.hasNext()) {String key = keys.next();if (key.startsWith(sameKeyPart)) {keys.remove(); // 使用Iterator的remove方法System.out.println("已删除: " + key);}
}
  1. 使用Java 8+ 的removeIf方法
list.removeIf(item -> "item2".equals(item));

在我们的测试中,这种方法同样有效:

List<String> fruits = new ArrayList<>();
fruits.add("香蕉");
fruits.add("苹果");
fruits.add("橙子");fruits.removeIf(fruit -> fruit.equals("香蕉"));
System.out.println("删除后: " + fruits);

多线程环境下的异常分析

多线程环境下,即使使用了Iterator的remove方法,仍然可能发生ConcurrentModificationException,因为多个线程可能同时修改集合。

异常复现

List<String> list = new ArrayList<>();
// 初始化列表...// 线程1:遍历列表
new Thread(() -> {Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {try {Thread.sleep(100); // 模拟耗时操作String item = iterator.next(); // 可能抛出异常} catch (Exception e) {e.printStackTrace();}}
}).start();// 线程2:修改列表
new Thread(() -> {try {Thread.sleep(50);list.add("newItem"); // 修改集合结构} catch (Exception e) {e.printStackTrace();}
}).start();

在我们的实际测试中,我们创建了一个更完整的示例:

private static void demoMultiThreadWithArrayList() {List<String> list = new ArrayList<>();for (int i = 0; i < 10; i++) {list.add("Item " + i);}// 创建一个线程用于遍历列表Thread readerThread = new Thread(() -> {try {System.out.println("读取线程开始遍历");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String item = iterator.next();Thread.sleep(100); // 模拟处理时间System.out.println("读取线程: " + item);}System.out.println("读取线程完成遍历");} catch (ConcurrentModificationException e) {System.out.println("读取线程捕获异常: " + e.getClass().getName());} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 创建一个线程用于修改列表Thread writerThread = new Thread(() -> {try {Thread.sleep(300); // 等待读取线程开始list.add("New Item"); // 添加新元素System.out.println("修改线程添加了新元素");Thread.sleep(100);list.remove(0); // 删除元素System.out.println("修改线程删除了元素");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});writerThread.start();readerThread.start();try {writerThread.join();readerThread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}

线程安全分析

在多线程环境下,ArrayList等非线程安全集合存在以下问题:

  1. 结构性修改的原子性:添加或删除元素不是原子操作
  2. 可见性问题:一个线程的修改对另一个线程不一定立即可见
  3. 一致性问题:迭代器可能看到集合的不一致状态

解决方案对比

解决方案适用场景优点缺点
Collections.synchronizedList读写频率相近简单易用性能较低,锁粒度大
CopyOnWriteArrayList读多写少读取无锁,性能高写入性能差,内存占用高
ConcurrentHashMap需要高并发Map分段锁,性能好仅适用于Map
CopiedIterator(自定义)读写分离场景避免长时间锁定额外内存开销
快照技术一次性读取后修改简单直观不适合大数据量
Stream API函数式处理代码简洁,可并行Java 8+才支持

在我们的测试中,我们对几种主要的解决方案进行了实际验证:

1. Collections.synchronizedList

List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// 需要注意的是,遍历时仍需要手动同步
synchronized (synchronizedList) {Iterator<String> iterator = synchronizedList.iterator();while (iterator.hasNext()) {String item = iterator.next();System.out.println("读取线程: " + item);}
}

2. CopyOnWriteArrayList

List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
// 可以安全地在遍历过程中修改
for (String item : copyOnWriteList) {System.out.println("当前元素: " + item);copyOnWriteList.add("New Item"); // 不会抛出异常
}

CopiedIterator实现与分析

CopiedIterator是一种自定义解决方案,它在创建迭代器时复制集合内容,从而避免并发修改异常。

实现代码

public static class CopiedIterator<E> implements Iterator<E> {private Iterator<E> iterator = null;public CopiedIterator(Iterator<E> itr) {LinkedList<E> list = new LinkedList<>();while(itr.hasNext()) {list.add(itr.next());}this.iterator = list.iterator();}public boolean hasNext() {return this.iterator.hasNext();}public void remove() {throw new UnsupportedOperationException("这是一个只读迭代器");}public E next() {return this.iterator.next();}
}

使用方式

List<String> list = new ArrayList<>();
// 初始化列表...// 创建CopiedIterator
Iterator<String> safeIterator;
synchronized(list) {safeIterator = new CopiedIterator<>(list.iterator());
}// 安全遍历,不会抛出ConcurrentModificationException
while(safeIterator.hasNext()) {String item = safeIterator.next();// 处理元素...
}

在我们的实际测试中,我们发现这种方案在特定场景下非常有效:

public static void perform() {Iterator<String> iterator;synchronized(list) {iterator = new CopiedIterator<>(list.iterator());}System.out.println("获取到只读迭代器,开始遍历");while (iterator.hasNext()) {String item = iterator.next();System.out.println("遍历元素: " + item);try {Thread.sleep(100); // 模拟处理时间} catch (InterruptedException e) {Thread.currentThread().interrupt();}}System.out.println("遍历完成");
}

优缺点分析

优点

  • 避免了长时间锁定集合
  • 适用于任何实现了Iterator接口的集合
  • 实现简单,容易理解

缺点

  • 额外的内存开销,尤其是对大型集合
  • 只能提供集合的快照,无法反映后续修改
  • 不支持修改操作(如remove)

在我们的性能测试中,我们发现对于包含10000个元素的列表,CopiedIterator的额外开销大约为10-15毫秒,这对于需要长时间处理的场景来说是可以接受的。

高级解决方案

1. ConcurrentHashMap

ConcurrentHashMap是一个高性能的线程安全Map实现,它使用分段锁技术提高并发性能。

Map<String, String> concurrentMap = new ConcurrentHashMap<>();
// 可以安全地在遍历过程中修改
for (String key : concurrentMap.keySet()) {concurrentMap.put("newKey", "newValue"); // 不会抛出异常
}

在我们的测试中,我们验证了ConcurrentHashMap的线程安全性:

private static void demoConcurrentHashMap() {Map<String, String> concurrentMap = new ConcurrentHashMap<>();concurrentMap.put("key1", "value1");concurrentMap.put("key2", "value2");// 测试ConcurrentHashMapfor (String key : concurrentMap.keySet()) {if (key.equals("key2")) {concurrentMap.put("key4", "value4"); // 不会抛出异常System.out.println("添加了新键值对: key4=value4");}}System.out.println("ConcurrentHashMap最终大小: " + concurrentMap.size());
}

2. CopyOnWriteArrayList/Set

CopyOnWriteArrayListCopyOnWriteArraySet在每次写操作时都会复制整个底层数组,非常适合读多写少的场景。

List<String> cowList = new CopyOnWriteArrayList<>();
// 可以安全地在遍历过程中修改
for (String item : cowList) {cowList.add("newItem"); // 不会抛出异常
}

我们的测试代码验证了这一点:

private static void demoCopyOnWriteArraySet() {// 创建CopyOnWriteArraySetSet<String> cowSet = new CopyOnWriteArraySet<>();cowSet.add("item1");cowSet.add("item2");cowSet.add("item3");System.out.println("\n尝试在遍历CopyOnWriteArraySet时修改:");for (String item : cowSet) {System.out.println("当前元素: " + item);cowSet.add("item4"); // 不会抛出异常}System.out.println("CopyOnWriteArraySet内容: " + cowSet);
}

3. 快照技术

快照技术是一种简单的解决方案,适用于一次性读取后修改的场景。

List<String> originalList = new ArrayList<>();
// 初始化列表...// 创建快照
List<String> snapshot = new ArrayList<>(originalList);// 遍历快照,修改原始列表
for (String item : snapshot) {if (someCondition(item)) {originalList.remove(item);}
}

我们在测试中也验证了这种技术:

private static void demoSnapshotTechnique() {List<String> originalList = new ArrayList<>();originalList.add("item1");originalList.add("item2");originalList.add("item3");System.out.println("原始列表: " + originalList);List<String> snapshot = new ArrayList<>(originalList);System.out.println("遍历快照并修改原始列表:");for (String item : snapshot) {System.out.println("当前元素: " + item);if (item.equals("item2")) {originalList.remove(item);}}System.out.println("修改后原始列表: " + originalList);System.out.println("快照内容保持不变: " + snapshot);
}

4. Stream API

Java 8引入的Stream API提供了一种函数式处理集合的方式,可以避免显式迭代。

List<String> result = list.stream().filter(item -> !item.equals("item2")).collect(Collectors.toList());

在我们的测试中,我们使用了Stream API的各种功能:

private static void demoStreamAPI() {List<String> list = new ArrayList<>();list.add("apple");list.add("banana");list.add("grape");System.out.println("\n使用Stream API过滤元素:");List<String> filteredList = list.stream().filter(item -> !item.equals("banana")).collect(Collectors.toList());System.out.println("过滤后: " + filteredList);System.out.println("\n使用Stream API转换元素:");List<String> upperCaseList = list.stream().map(String::toUpperCase).collect(Collectors.toList());System.out.println("转换后: " + upperCaseList);
}

性能测试与对比

我们对不同解决方案进行了性能测试,以下是结果分析:

1. 遍历性能对比(10,000元素)

解决方案平均耗时(ms)
普通Iterator1-2
CopiedIterator10-15
CopyOnWriteArrayList1-2
Collections.synchronizedList3-5
Stream API (顺序)5-8
Stream API (并行)2-4

在我们的性能测试代码中,我们进行了实际测量:

private static void performanceTest() {// 准备大数据集List<String> largeList = new ArrayList<>();for (int i = 0; i < 10000; i++) {largeList.add("Item-" + i);}// 测试普通Iteratorlong startTime = System.nanoTime();Iterator<String> normalIterator = largeList.iterator();int count = 0;while (normalIterator.hasNext()) {normalIterator.next();count++;}long normalTime = System.nanoTime() - startTime;// 测试CopiedIteratorstartTime = System.nanoTime();Iterator<String> copiedIterator = new CopiedIterator<>(largeList.iterator());count = 0;while (copiedIterator.hasNext()) {copiedIterator.next();count++;}long copiedTime = System.nanoTime() - startTime;System.out.println("普通Iterator遍历时间: " + TimeUnit.NANOSECONDS.toMillis(normalTime) + " 毫秒");System.out.println("CopiedIterator遍历时间: " + TimeUnit.NANOSECONDS.toMillis(copiedTime) + " 毫秒");System.out.println("CopiedIterator额外开销: " + (copiedTime - normalTime) / 1000000.0 + " 毫秒");
}

2. 修改性能对比(10,000元素,添加操作)

解决方案平均耗时(ms)
ArrayList0.1-0.2
CopyOnWriteArrayList50-100
Collections.synchronizedList0.5-1
ConcurrentHashMap (put)0.2-0.5

3. 内存占用对比

解决方案相对内存占用
ArrayList1x
CopiedIterator2x
CopyOnWriteArrayList (写操作时)2x
快照技术2x

异常处理机制深入分析

fail-fast机制原理

Java集合框架中的fail-fast机制是一种错误检测机制,它能帮助开发者尽早发现程序中的并发修改问题。当多个线程对集合进行结构上的改变时,就可能产生fail-fast事件。

在ArrayList中,modCount变量记录了集合结构修改的次数。每次调用add、remove等修改结构的方法时,modCount都会增加。同时,Iterator在创建时会保存当前的modCount值作为expectedModCount。每次调用Iterator的next()方法时,都会检查modCount是否与expectedModCount相等,如果不相等则抛出ConcurrentModificationException。

// ArrayList中的add方法
public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;
}// AbstractList中的ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}modCount++; // 修改计数器增加ensureExplicitCapacity(minCapacity);
}

异常传播与处理

在实际应用中,我们需要合理处理ConcurrentModificationException异常。以下是我们推荐的处理方式:

  1. 预防为主:使用线程安全的集合类或同步机制来避免异常的发生
  2. 捕获并记录:在无法避免异常的情况下,捕获异常并记录日志
  3. 优雅降级:提供备选方案,确保系统在异常情况下仍能正常运行
public class SafeListProcessor {private List<String> dataList;public SafeListProcessor(List<String> dataList) {this.dataList = dataList;}public void processList() {Iterator<String> iterator = null;synchronized(dataList) {iterator = new CopiedIterator<>(dataList.iterator());}try {while (iterator.hasNext()) {String item = iterator.next();// 处理元素processItem(item);}} catch (ConcurrentModificationException e) {// 记录异常日志System.err.println("检测到并发修改异常: " + e.getMessage());// 可以选择重试或使用备选方案handleConcurrentModification();}}private void processItem(String item) {// 处理单个元素System.out.println("处理元素: " + item);}private void handleConcurrentModification() {// 处理并发修改异常的备选方案System.out.println("使用备选方案处理数据");}
}

实际应用建议

选择合适的解决方案

在实际项目中,我们需要根据具体场景选择合适的解决方案:

  1. 读多写少场景
    • 推荐使用CopyOnWriteArrayList/CopyOnWriteArraySet
    • 适用于缓存、配置信息等场景
  2. 高并发读写场景
    • 推荐使用ConcurrentHashMap
    • 适用于需要高并发访问的Map结构
  3. 需要长时间遍历的场景
    • 推荐使用CopiedIterator或快照技术
    • 适用于需要对大量数据进行复杂处理的场景
  4. 简单过滤或转换场景
    • 推荐使用Stream API
    • 代码简洁,可读性强

代码示例

以下是我们项目中实际使用的代码示例:

// 使用CopyOnWriteArrayList处理配置信息
public class ConfigManager {private CopyOnWriteArrayList<ConfigItem> configItems = new CopyOnWriteArrayList<>();public void addConfig(ConfigItem item) {configItems.add(item);}public List<ConfigItem> getActiveConfigs() {// 可以安全地遍历,即使其他线程正在修改return configItems.stream().filter(ConfigItem::isActive).collect(Collectors.toList());}
}// 使用ConcurrentHashMap处理用户会话
public class SessionManager {private ConcurrentHashMap<String, UserSession> sessions = new ConcurrentHashMap<>();public void addSession(String sessionId, UserSession session) {sessions.put(sessionId, session);}public void cleanupExpiredSessions() {// 可以安全地遍历并修改sessions.entrySet().removeIf(entry -> entry.getValue().isExpired());}
}// 使用CopiedIterator处理长时间运行的任务
public class DataProcessor {private List<DataItem> dataItems;public void processLargeDataSet() {Iterator<DataItem> iterator;synchronized(dataItems) {iterator = new CopiedIterator<>(dataItems.iterator());}// 长时间处理不会阻塞其他线程对dataItems的修改while (iterator.hasNext()) {DataItem item = iterator.next();processComplexCalculation(item);}}
}

性能优化建议

  1. 合理预估集合大小
    • 使用带初始容量的构造函数避免频繁扩容
    • 例如:new ArrayList<>(1000)而不是new ArrayList<>();
  2. 选择合适的数据结构
    • 频繁随机访问:ArrayList
    • 频繁插入删除:LinkedList
    • 需要排序:TreeSet/TreeMap
    • 唯一性要求:HashSet/HashMap
  3. 减少锁竞争
    • 缩小同步块范围
    • 使用读写锁分离读写操作
    • 考虑使用无锁数据结构

最佳实践总结

单线程环境

  1. 避免在for-each循环中修改集合
    • 使用Iterator的remove()方法
    • 使用Java 8+的removeIf()、replaceAll()等方法
    • 创建集合副本进行遍历
  2. 批量操作优于单个操作
    • 使用addAll()、removeAll()等批量方法
    • 使用Stream API进行批量处理

在我们的测试中,我们发现removeIf()方法特别适用于简单的过滤操作:

List<String> fruits = new ArrayList<>();
fruits.add("香蕉");
fruits.add("苹果");
fruits.add("橙子");// 使用removeIf进行过滤
fruits.removeIf(fruit -> fruit.equals("香蕉"));
System.out.println("删除后: " + fruits);

多线程环境

  1. 选择合适的线程安全集合
    • 读多写少:CopyOnWriteArrayList/Set
    • 读写频率相近:Collections.synchronizedList + 同步块
    • 高并发Map:ConcurrentHashMap
  2. 避免长时间锁定集合
    • 使用CopiedIterator或快照技术
    • 缩小同步块范围
  3. 考虑使用并发工具类
    • BlockingQueue系列
    • ConcurrentSkipListMap/Set

在我们的多线程测试中,我们发现CopyOnWriteArrayList在读多写少的场景下表现优异:

private static void demoCopyOnWriteArrayList() {List<String> copyOnWriteList = new CopyOnWriteArrayList<>();for (int i = 0; i < 10; i++) {copyOnWriteList.add("Item " + i);}Thread readerThread = new Thread(() -> {System.out.println("读取线程开始遍历CopyOnWriteArrayList");Iterator<String> iterator = copyOnWriteList.iterator();while (iterator.hasNext()) {String item = iterator.next();System.out.println("读取线程: " + item);try {Thread.sleep(100); // 模拟处理时间} catch (InterruptedException e) {Thread.currentThread().interrupt();}}System.out.println("读取线程完成遍历");});Thread writerThread = new Thread(() -> {try {System.out.println("修改线程开始修改CopyOnWriteArrayList");copyOnWriteList.add("New Item"); // 添加新元素System.out.println("修改线程添加了新元素");Thread.sleep(100);copyOnWriteList.remove(0); // 删除元素System.out.println("修改线程删除了元素");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});readerThread.start();writerThread.start();try {readerThread.join();writerThread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("最终列表大小: " + copyOnWriteList.size());
}

性能优化

  1. 根据访问模式选择集合
    • 随机访问多:ArrayList
    • 插入删除多:LinkedList
    • 唯一性要求:HashSet/TreeSet
  2. 预估集合大小
    • 使用构造函数指定初始容量
    • 避免频繁扩容
  3. 减少不必要的复制
    • 谨慎使用CopyOnWrite集合
    • 优化CopiedIterator实现

在我们的测试中,我们发现对于大数据集,Stream API的并行处理能力非常强大:

private static void demoParallelStream() {List<Integer> numbers = new ArrayList<>();for (int i = 0; i < 1000; i++) {numbers.add(i);}System.out.println("\n使用并行流处理大量数据:");long startTime = System.nanoTime();int sum = numbers.stream().mapToInt(Integer::intValue).sum();long sequentialTime = System.nanoTime() - startTime;startTime = System.nanoTime();int parallelSum = numbers.parallelStream().mapToInt(Integer::intValue).sum();long parallelTime = System.nanoTime() - startTime;System.out.println("顺序流处理时间: " + TimeUnit.NANOSECONDS.toMicros(sequentialTime) + " 微秒");System.out.println("并行流处理时间: " + TimeUnit.NANOSECONDS.toMicros(parallelTime) + " 微秒");System.out.println("结果验证: " + (sum == parallelSum ? "正确" : "错误"));
}
http://www.dtcms.com/a/367230.html

相关文章:

  • 从群体偏好到解构对齐:大模型强化学习从GRPO到DAPO的“认知”进化
  • https + 域名 + 客户端证书访问模式
  • Python中将方法转为属性式访问
  • Flutter之riverpod状态管理详解
  • 【计算机网络(自顶向下方法 第7版)】第一章 计算机网络概述
  • 从零开始的python学习——元组
  • 晨控CK-GW08S与汇川H5U系列PLC配置Ethernet/IP通讯连接手册
  • 别再跟风通用大模型了!企业自建专属 AI 大模型的「避坑指南 + 落地干货」
  • GitHub每日最火火火项目(9.4)
  • Linux命令和使用
  • 【数学建模学习笔记】机器学习回归:决策树回归
  • Qt---状态机框架QState
  • Java ForkJoin
  • 办公任务分发项目 laravel vue mysql 第一章:核心功能构建 API
  • Dify 低代码平台技术详解与实践
  • 实验室智能化管理信息系统如何重塑实验室运作模式?
  • Linux系统shell脚本(三)
  • 解密注意力计算的并行机制:从多头并张量操作到CUDA内核优化
  • 【Luogu_P5839】 [USACO19DEC] Moortal Cowmbat G【动态规划】
  • C语言(长期更新)第14讲:指针详解(四)
  • 第六章 Cesium 实现简易河流效果
  • FastDDS:第三节(3.2小节)
  • 规则引擎开发现在已经演化成算法引擎了
  • #T1359. 围成面积
  • Java并发编程:sleep()与wait()核心区别详解
  • 通过Interface扫描获取所有其实现类
  • AI 浪潮下阿里云“高光”乍现,但离终局胜利尚远
  • MySQL主从复制进阶(GTID复制,半同步复制)
  • 搭建基于 Solon AI 的 Streamable MCP 服务并部署至阿里云百炼
  • 鸿蒙NEXT动画开发指南:组件与页面典型动画场景解析