我在 Java 里用 Arrays.asList 把数组转成 List,以为得到了一个普通的可增删的列表,结果往里 add 一个元素就抛 UnsupportedOperationException,因为它根本不是 ArrayList 而是个固定大小的数组视图的深度复盘

我想把数组转成 List 后做些增删,顺手写了 List list = Arrays.asList("a","b","c"),然后 list.add("d")——Arrays.asList 返回 List、List 当然能 add,多自然。结果一运行就崩:UnsupportedOperationException,栈指向 add。返回类型明明是 List、接口里明明有 add,怎么一调就说不支持?复盘才搞懂:Arrays.asList 返回的不是 java.util.ArrayList,而是 Arrays 内部一个私有的固定大小的 ArrayList(Arrays$ArrayList,和常用的完全是两个类),它是原数组的视图、底层直接用着传入的数组、大小固定;所以支持 get/set,但不支持任何改变大小的操作(add/remove);而 List 接口把 add/remove 定义成可选操作,实现可以选择不支持、直接抛 UnsupportedOperationException。根因是我把实现了 List 接口当成了支持全部 List 操作。这篇复盘从故障现场讲到 Arrays.asList 的真相、List 的可选操作机制、各种不可变/固定 List 来源,再到要可变就 new ArrayList<>(Arrays.asList(...))、要不可变用 List.of、基本类型数组用 stream.boxed 的完整正解,以及其他看起来支持实际不支持的坑,和属于某类别不等于具备该类别全部能力、接口只规定外形不保证每个实现都健全、要核实具体实例真实契约的认知。

我在 Java 里用 Arrays.asList 把数组转成 List,以为得到了一个普通的可增删的列表,结果往里 add 一个元素就抛 UnsupportedOperationException,因为它根本不是 ArrayList 而是个固定大小的数组视图:一次误把"实现了 List 接口"当成"支持全部 List 操作"的深度复盘

那个 UnsupportedOperationException,是我写一段"再普通不过"的列表操作时撞上的。我有个数组(或几个元素),想转成 List 后做些增删,顺手写了 List<String> list = Arrays.asList("a", "b", "c");,然后 list.add("d");——多自然啊,Arrays.asList 返回个 List,List 当然能 add。结果一运行就崩:java.lang.UnsupportedOperationException(不支持的操作),异常栈指向 add 那一行。我当时就懵了:返回类型明明是 List,List 接口里明明有 add 方法,怎么一调就说不支持?复盘 Arrays.asList 的实现,我才彻底搞懂,后背发凉:问题出在我以为"它返回 List,就支持 List 的所有操作"。Arrays.asList 返回的不是 java.util.ArrayList,而是 Arrays 类内部一个私有的、固定大小的 ArrayList(全名 java.util.Arrays$ArrayList,和我们常用的完全是两个类);它是原数组的一个"视图(view)"——底层直接用着传入的那个数组,大小和数组绑定、固定不变;所以它支持 getset(改元素)、遍历,但不支持任何"改变大小"的操作(addremove)——因为底层数组的长度是固定的,没法增删;List 接口把 add/remove 定义成了"可选操作(optional operation)",实现类可以选择不支持、直接抛 UnsupportedOperationException——Arrays.asList 返回的这个视图正是这么干的。根本原因是:Arrays.asList 返回的是一个底层绑定原数组、固定大小的 List 视图,不支持 add/remove(List 接口的这些是可选操作,它选择抛 UnsupportedOperationException);我误以为"返回类型是 List 就支持全部 List 操作",才在它上面 add 而崩溃。问题的根,是误解了 Arrays.asList——它返回的是固定大小的数组视图而非可变 ArrayList,不支持增删;我把"实现了 List 接口"当成了"支持全部 List 操作"。这篇就把这次"Arrays.asList 固定大小"的坑,从头到尾复盘一遍。

故障现场:返回的是 List,add 却抛异常

问题在于 Arrays.asList 返回的是固定大小的数组视图:

// 我以为得到一个普通可变 List(其实是固定大小的数组视图):
List list = Arrays.asList("a", "b", "c");

list.add("d");      // ✗ java.lang.UnsupportedOperationException!
list.remove("a");   // ✗ 同样 UnsupportedOperationException!
list.set(0, "x");   // ✓ 这个可以(改元素不改变大小)
String s = list.get(1);  // ✓ 这个也可以

/*
为什么 add/remove 抛异常:
  - Arrays.asList 返回的不是 java.util.ArrayList, 而是 java.util.Arrays$ArrayList
    (Arrays类的一个私有静态内部类, 名字凑巧也叫ArrayList, 但完全是另一个类!);
  - 它是传入数组的一个"视图": 底层直接引用那个数组, 大小 = 数组长度, 固定不可变;
  - 它继承自 AbstractList, 只重写了 get/set/size 等; 没重写 add/remove;
  - AbstractList 里 add/remove 的默认实现就是抛 UnsupportedOperationException;
  - 所以: get/set(不改大小)能用, add/remove(改大小)直接抛异常。

List 接口的"可选操作(optional operation)"机制:
  - List 接口的 add/remove/clear 等被文档标注为 "optional operation";
  - 意思是: 实现类"可以"不支持它们, 不支持时按约定抛 UnsupportedOperationException;
  - 所以"一个对象是 List" 不代表 "它支持 List 的所有方法"——要看具体实现的契约。
  - 类似的还有: List.of(...) / Collections.unmodifiableList(...) 返回的不可变列表,
    add/remove/set 全都抛 UnsupportedOperationException。

还有一个隐藏坑(视图的双向联动):
  String[] arr = {"a", "b", "c"};
  List list = Arrays.asList(arr);
  list.set(0, "X");        // 改list → arr[0]也变成"X"(它俩共享底层数组!)
  arr[1] = "Y";            // 改arr → list.get(1)也变成"Y";
  // 因为list就是arr的视图, 改一个影响另一个(类似Go切片共享底层数组352篇)。

★ 关键: Arrays.asList 返回固定大小的数组视图, 不是可变ArrayList; 支持get/set, 不支持add/remove;
  "实现了List接口" ≠ "支持List的全部操作"(add/remove是可选操作, 实现可不支持)。
*/

看着那行指向 addUnsupportedOperationException,我又错愕又恍然:"返回类型明明写着 List,IDE 也提示有 add 方法、编译毫无问题,我怎么会想到它运行时不支持 add?原来'是个 List'和'支持 List 所有操作'根本是两码事。"这个坑最隐蔽的地方在于:编译期完全正常——list 的静态类型是 List,addList 接口的方法,IDE 有补全、编译器不报错;问题只在运行时调用 add 那一刻才爆发;而且 get/set 又是好用的,更容易让人误以为它就是个普通 List下面就来拆解,各种 List 的差异到底在哪。

第一件事:搞懂 Arrays.asList 与可选操作

我顺着这次事故,把各种 List 的来源和契约彻底理清了。

Arrays.asList 到底返回什么? 为什么 add 会抛异常?

【核心: Arrays.asList返回固定大小的数组视图(Arrays$ArrayList), 支持get/set不支持add/remove;
   List接口的add/remove是"可选操作", 实现可不支持并抛UnsupportedOperationException; "是List"≠"支持全部List操作"】

1. Arrays.asList 的真相:
   - 返回 java.util.Arrays$ArrayList(私有内部类), 不是 java.util.ArrayList!
   - 它是传入数组的"视图": 底层引用那个数组, 大小固定 = 数组长度;
   - 支持: get、set、size、遍历、indexOf 等(不改变大小的);
   - 不支持: add、remove、clear 等(改变大小的)→ 抛 UnsupportedOperationException;
   - 副作用: 它和原数组共享存储, 改一个影响另一个(双向视图)。

2. List 接口的"可选操作(optional operation)":
   - List(及Collection)接口里, add/remove/set/clear 等被文档标为 "optional";
   - 实现类可以选择不支持, 不支持时按规范抛 UnsupportedOperationException;
   - 所以"声明类型是List"只保证"有这些方法可调", 不保证"调了一定能成功";
   - 这是接口设计的一种权衡(让不可变/固定视图也能实现List接口)。

3. 几种"List但不可变/固定"的常见来源(都可能抛UnsupportedOperationException):
   - Arrays.asList(...)        : 固定大小, 支持set, 不支持add/remove;
   - List.of(...)(Java9+)     : 完全不可变, set/add/remove都不支持; 还不允许null;
   - Collections.unmodifiableList(l) : 只读视图, 任何修改都不支持;
   - Collections.emptyList()/singletonList(): 不可变;
   - Stream.collect(Collectors.toList()) : 不保证可变性(规范上)、不保证类型;
     要可变明确用 Collectors.toCollection(ArrayList::new)。

4. 正确做法(要可变就显式构造可变List):
   - 要可变: new ArrayList<>(Arrays.asList(...)) 或 new ArrayList<>(List.of(...));
     → 把元素拷贝进一个真正的、可增删的 ArrayList;
   - 只读/共享: 明确想要不可变就用 List.of / unmodifiableList(并知道它不可变);
   - 想根据需求选对: "我需要增删吗?" → 需要就构造ArrayList, 不需要可用视图/不可变。

5. 本质: 别用"静态类型/接口"推断"运行时能力"
   - 一个对象的静态类型是 List, 只说明"它承诺实现了List接口的方法签名";
   - 不代表"它支持每个方法的全部语义"(可选操作可能不支持);
   - 要了解你拿到的"具体实现"是什么、它的实际契约(可变?固定?只读?)。

一句话: Arrays.asList返回固定大小的数组视图(支持set不支持add/remove)、与原数组共享存储;
   List的add/remove是可选操作、实现可不支持; 要可变就new ArrayList<>(...)包一层; "是List"≠"支持全部List操作"。

这套认知,是整个坑的根。Arrays.asList 的真相:返回 Arrays$ArrayList(私有内部类)而非 java.util.ArrayList,是原数组的视图、大小固定;支持 get/set/遍历,不支持 add/remove;且与原数组共享存储可选操作:List 接口的 add/remove/set/clear 被标为 optional,实现可不支持、抛 UnsupportedOperationException;"声明类型是 List"只保证有方法可调、不保证调了能成其他不可变来源:List.of(完全不可变还禁 null)、unmodifiableList(只读视图)、emptyList/singletonList、Collectors.toList(不保证可变)正确做法:要可变就 new ArrayList<>(Arrays.asList(...)) 拷进真正可增删的 ArrayList;按"需不需要增删"选对类型本质:别用静态类型/接口推断运行时能力,要了解拿到的具体实现的实际契约一句话:Arrays.asList 返回固定大小的数组视图(支持 set 不支持 add/remove)、与原数组共享存储;List 的 add/remove 是可选操作、实现可不支持;要可变就 new ArrayList<>(...) 包一层;"是 List"≠"支持全部 List 操作"。

第二件事:正解——要可变就显式构造 ArrayList

知道了它是固定视图,正解就清楚了:按需求选对 List,要增删就显式构造可变的。

// 正解1: 要可变(能add/remove)→ 用 new ArrayList<>(...) 包一层(本次该做的)
List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.add("d");      // ✓ 真正的 ArrayList, 支持增删
list.remove("a");   // ✓ OK
// 把 Arrays.asList 的视图里的元素, 拷贝进一个真正的、可增删的 ArrayList。

// 正解2: 只是要个不可变列表(不增删)→ 明确用 List.of(语义清晰)
List immutable = List.of("a", "b", "c");   // Java9+, 完全不可变, 意图明确
// 之后任何 add/remove/set 都会抛异常——但这是你"想要的不可变", 不是意外。

// 正解3: 数组转可变 List 的几种写法
String[] arr = {"a", "b", "c"};
List l1 = new ArrayList<>(Arrays.asList(arr));        // 可变
List l2 = Arrays.stream(arr).collect(Collectors.toCollection(ArrayList::new)); // 可变, 明确
// List l3 = Arrays.asList(arr);                      // 固定视图(改元素会联动arr!)

// 正解4: 想避免"共享底层数组"的联动副作用, 也要拷贝
String[] arr2 = {"a", "b"};
List view = Arrays.asList(arr2);     // view 和 arr2 共享, 改一个影响另一个
List copy = new ArrayList<>(view);   // copy 是独立拷贝, 与 arr2 互不影响

// 正解5: 单元素别用 Arrays.asList 的陷阱(基本类型数组)
int[] nums = {1, 2, 3};
// List wrong = Arrays.asList(nums);   // ✗ 得到 List(size=1)! 不是List!
List right = Arrays.stream(nums).boxed().collect(Collectors.toList());  // ✓

// 核心: 按需求选——要增删用 new ArrayList<>(Arrays.asList(...)); 要不可变用 List.of;
//   清楚 Arrays.asList 是固定视图、与原数组联动; 要独立可变副本就显式拷贝。

这套正解的关键,是按"我到底需要什么样的 List"来选对构造方式,而不是默认 Arrays.asList 给的就是普通可变列表要可变:new ArrayList<>(Arrays.asList(...)) 把元素拷进真正可增删的 ArrayList——这正是本次我该做的。要不可变:明确用 List.of(...),意图清晰,之后的 UnsupportedOperationException 是你想要的而非意外。避免联动副作用:想要独立副本就 new ArrayList<>(view) 拷贝,别让它和原数组共享。基本类型数组的陷阱:Arrays.asList(int[]) 会得到 List<int[]>(只有一个元素),要用 stream().boxed()。

第三件事:其他几个"看起来支持、实际不支持"的坑

顺着这次 Arrays.asList,我把 Java 里"类型上有这能力、运行时却不支持"的几类坑也一并理了:

几类"声明有、运行时不支持/不符预期"的坑:

坑1: List.of/Set.of/Map.of 不可变且不允许null——往里add或传null都抛异常;
   正解: 要可变/可含null用 new ArrayList<>/HashMap<>; 它们是不可变集合, 别误用。

坑2: Collections.unmodifiableList 是"只读视图"不是"拷贝"——
   原list改了, 这个视图也跟着变(只是你不能通过视图改); 要真正快照得自己拷贝。

坑3: Stream.collect(Collectors.toList())不保证返回可变List(规范上)——
   多数实现现在返回ArrayList但别依赖; 要明确可变用 toCollection(ArrayList::new); JDK16+有toList()返回不可变。

坑4: subList 返回的是原list的视图——改subList影响原list, 结构性改原list又会让subList失效;
   正解: 要独立用 new ArrayList<>(list.subList(...))。

坑5: 把不可变/固定List传给会修改它的方法——方法内部add就炸;
   正解: 接口约定清楚(要不要可变), 或防御性拷贝。

坑6: 数组和List混淆——Arrays.asList(基本类型数组)得到List<数组>而非List<元素>(上文)。

共同的根: 一个对象"在类型上声明实现了某接口/有某方法", 不等于"它在运行时支持该方法的完整语义";
   接口可能有"可选操作", 实现可能是"视图/只读/固定大小"; 要了解你手上"具体实现"的真实契约,
   别仅凭"它是个List/它有这方法"就假设它什么都能干。

这些坑看似不同,根却是同一个:一个对象"在类型/接口上声明有某个方法",不等于"它在运行时真的支持这个方法的完整语义"——接口可能定义了"可选操作",而具体实现可能是个"视图、只读、固定大小"的特殊版本,只支持其中一部分认清这个根("类型声明 ≠ 运行时能力,要看具体实现的真实契约"),才不会被"它是个 List"误导。

第四件事:各种 List 来源的能力——两张对照表

我把常见 List 来源的可变性、以及操作支持情况,整理成对照表,贴在了团队的 Java 规范里:

List 来源 add/remove set 改元素 特点
new ArrayList<>() ✓ 支持 ✓ 支持 真正的可变列表
Arrays.asList(...) ✗ 抛异常 ✓ 支持 固定大小,联动原数组
List.of(...) ✗ 抛异常 ✗ 抛异常 完全不可变,禁 null
Collections.unmodifiableList ✗ 抛异常 ✗ 抛异常 只读视图,随原 list 变
Collections.emptyList() ✗ 抛异常 ✗ 抛异常 不可变空列表
list.subList(a,b) △ 影响原 list △ 影响原 list 原 list 的视图
需求 该用什么
要增删的列表 new ArrayList<>(...)
明确不可变(防误改) List.of(...)
数组转可变 List new ArrayList<>(Arrays.asList(arr))
基本类型数组转 List Arrays.stream(arr).boxed()...
只读暴露内部 list unmodifiableList(知道是视图)
独立快照 new ArrayList<>(原 list)

这两张表的核心,第一张是不同来源的 List 能力差异巨大——只有 new ArrayList 是"什么都支持"的,其他大多有"不可变/固定/视图"的限制;第二张是按需求选对——要增删就显式 new ArrayList。记住一条:看到一个 List 变量,别假设它能增删;要增删,就确保它来自 new ArrayList<>(...)

第五件事:关于 Arrays.asList 的几组容易想当然的认知

这次事故也让我厘清了几组关于 Arrays.asList 和 List 的、容易想当然的概念:

直觉以为 实际上
Arrays.asList 返回 ArrayList 返回 Arrays$ArrayList,是另一个类
返回类型是 List 就能 add add 是可选操作,这个实现不支持
编译不报错就运行没问题 add 编译过,运行时才抛 UnsupportedOperation
它是独立的列表 是原数组的视图,改一个影响另一个
是 List 就支持 List 所有操作 "是 List"只保证有方法签名,不保证都支持
List.of 和 Arrays.asList 差不多 List.of 连 set 都不支持,还禁 null
Arrays.asList(int[]) 得到 List<Integer> 得到 List<int[]>,只有一个元素

这张表里,我栽的是第一行和第五行:把"返回类型是 List"和"就是个 ArrayList、支持所有 List 操作"画了等号,没意识到 List 是接口、不同实现的能力可以差很多厘清这些,核心是一个意识:接口(List)只规定了"有哪些方法",不规定"每个实现都完整支持它们";拿到一个 List,要清楚它的"具体实现"是可变的、固定的、还是只读的——别凭接口类型假设它的运行时能力。

第六件事:把数组/元素转成 List 时,我现在的自检习惯

现在每当我要把数组或几个元素转成 List,我都会先按这张图问自己:

这张图的精髓,是"要增删就 new ArrayList 包、要不可变用 List.of、基本类型数组用 stream.boxed"先问会不会增删(会就 new ArrayList)、再看要不要不可变(要就 List.of)、注意基本类型数组别直接 asList这套习惯,让我从"asList 拿来就当普通 List 用"变成了"按需求选对、清楚它的契约"——核心始终是:Arrays.asList 返回固定大小的数组视图、支持 set 不支持 add/remove、与原数组联动;要可变就 new ArrayList<>(...) 包一层,"是 List"不等于"支持全部 List 操作"。

我立下的几条规矩

这场"add 抛 UnsupportedOperationException"的事故,换来了我写 Java 集合时,刻进骨子里的几条铁律:

  1. Arrays.asList 返回的是固定大小的数组视图(Arrays$ArrayList),不是 java.util.ArrayList。
  2. 它支持 get/set,但不支持 add/remove(改大小)——会抛 UnsupportedOperationException。
  3. 它与原数组共享存储,改 List 元素会影响原数组,反之亦然。
  4. 要可变列表,用 new ArrayList<>(Arrays.asList(...)) 拷进真正的 ArrayList。
  5. 要不可变,明确用 List.of(...)(完全不可变、禁 null),让意图清晰。
  6. List 接口的 add/remove/set 是"可选操作",实现可不支持;"是 List"≠"支持全部 List 操作"。
  7. 别凭静态类型/接口假设运行时能力;拿到集合要清楚它的具体实现(可变/固定/只读)。

附:一份"安全转 List"的工具方法

借这次的坑,我给团队写了几个语义清晰的工具方法,把"转 List"的意图(要可变 / 要不可变 / 要快照)显式化,杜绝 Arrays.asList 的歧义。

public final class Lists {
    private Lists() {}

    /** 转成可变 List(能 add/remove), 这是最常见的需求 */
    @SafeVarargs
    public static  List mutableOf(T... items) {
        return new ArrayList<>(Arrays.asList(items));   // ✓ 真正可变
    }

    /** 数组转可变 List */
    public static  List mutableFrom(T[] arr) {
        return new ArrayList<>(Arrays.asList(arr));
    }

    /** 给一个集合做独立快照(不随原集合变, 也不影响原集合) */
    public static  List snapshot(Collection src) {
        return new ArrayList<>(src);
    }

    /** 明确要不可变(防误改, 意图清晰) */
    @SafeVarargs
    public static  List immutableOf(T... items) {
        return List.copyOf(Arrays.asList(items));       // 不可变
    }
}

// 调用处意图一目了然, 不会再误用 Arrays.asList:
List a = Lists.mutableOf("a", "b", "c");   // 我要可变的
a.add("d");                                         // ✓ 没问题
List b = Lists.immutableOf("x", "y");      // 我要不可变的(故意防改)

// 原则: 用"语义清晰、意图明确"的工厂方法, 替代"行为容易误解"的标准API(Arrays.asList);
//   让调用方一眼看出"这是可变还是不可变", 把契约写进方法名里, 而非留给人去记。

这套工具的价值不在代码量,而在把"可变 / 不可变 / 快照"这个最容易被 Arrays.asList 模糊掉的关键契约,显式地写进了方法名(mutableOf / immutableOf / snapshot)调用方一眼就知道自己拿到的是什么,不会再像我当初那样,对着一个"看起来是 List"的东西,在它不支持的操作上栽跟头。

多说一句我后来的感触:Arrays.asList 这个坑之所以经典,是因为它精准地踩在了「静态类型给的安全感」和「运行时实际行为」的裂缝上——编译器、IDE、类型系统都告诉你「这是个 List,有 add 方法,放心调」,你于是建立起了信任,而这份信任恰恰在运行时被一个 UnsupportedOperationException 击碎。它提醒我:类型系统是一道很有价值的保障,但它保障的是「方法签名层面的契约」,而非「每个实现的运行时语义都健全」;有些约定(比如「可选操作」)是类型系统表达不了、只写在文档里的,这部分只能靠我去读、去记、去核实。把对类型系统的信任,和对「具体实现实际行为」的核实,这两件事都做到位,才不至于在这种裂缝里反复栽跟头。

写在最后

回头看,这场由"误把 Arrays.asList 当普通可变 List"引发的 UnsupportedOperationException,真正教给我的,远不止"要可变就 new ArrayList"这一个技巧。它让我对"'一个东西属于某个类别、贴着某个标签'(是个 List), 和 '它真的具备这个类别所宣称的全部能力'(支持所有 List 操作), 是两回事; 类别/标签只是一种'承诺了某套接口'的分类, 不保证每个成员都名副其实地、完整地具备那套能力",有了一次刻骨的体会。我栽跟头,是因为我用"它的类型标签(List)"反推了它的"实际能力(能 add)"——我看到"List"这个标签, 就默认它拥有 List 接口承诺的一切;可我忽略了: "是个 List" 只意味着 "它声明实现了 List 这套接口(有这些方法签名)", 而不意味着 "它对每个方法都提供了完整、可用的实现"——List 接口本身就留了"可选操作"的口子, 允许成员"名义上是 List, 实际只支持一部分";我把"分类上的归属"误当成了"能力上的保证"这让我领悟到一个关于"分类、标签与实际能力"的深刻认知:"属于某个类别/实现了某个接口/拥有某个头衔" 是一种分类层面的声明,它规定的是"应该有哪些方面"(契约的外形),而非保证"每个方面都健全、可用、名副其实"(契约的实质);同一个类别下的成员, 完全可能能力参差不齐——有的"全能"(ArrayList), 有的只是"受限的特例"(固定视图、只读);所以不能仅凭"它是什么(类别)"就推断"它能做什么(能力)", 要进一步了解"它具体是哪一种实现、它的实际契约和限制是什么"这给了我一种面对"类别化的东西"时的清醒:当我依赖一个东西的某项能力时(尤其它来自一个抽象类型/接口/分类),不要满足于"它属于那个类别, 所以应该能",而要确认"我手上这个具体的实例/实现, 是否真的支持我要用的这项能力?它有没有什么限制?"——把'分类归属'和'具体能力'分开, 用'这个具体实现的实际契约'而非'它所属类别的理想承诺'来决定我怎么用它;"不被标签和分类迷惑、去核实具体实例的真实能力与限制",是避免'把名义当实质、把归类当保证'式误判的关键认清属于某类别不等于具备该类别全部能力、接口只规定外形不保证每个实现都健全、要核实具体实例的真实契约——这,是我用一次 Arrays.asList 的事故,换来的、关于 Java 集合、也关于如何区分分类与实际能力的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次用 Arrays.asList 后想 add 之前,顺手用 new ArrayList<>() 包一层,那我对着那行 UnsupportedOperationException 错愕的那一刻,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我用 WaitGroup 等一批 goroutine 全部干完,却把 wg.Add 写进了 goroutine 内部,结果主协程的 Wait 在 Add 之前就跑完直接返回,程序以为活儿都干完了其实大半还没开始:一次 WaitGroup 时序用错的深度复盘

2026-6-3 0:42:40

技术教程

我为了保证原子性把几万条记录的批量处理塞进了一个数据库事务,结果这个事务跑了好几分钟,期间长时间持锁阻塞了其他业务、占满了连接池、还把回滚段撑得老大、主从延迟飙升:一次大事务拖垮整个库的深度复盘

2026-6-3 0:55:15

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索