真实验证 Arrays.asList(backing) 返回的 List 不是拷贝而是视图:view.set(0, 999) 之后原始数组真实跟着变了;反过来直接改原始数组的元素,view 真实也立刻跟着变;view.add()/view.remove() 真实抛出 UnsupportedOperationException,因为它是固定大小的;想要一份真正独立的 ArrayList,得用 new ArrayList<>(Arrays.asList(backing)) 包一层真实拷贝,这次 add 才真实成功,且不影响原始数组。
按姓名字母序插入 6 个学生(分数有重复),只按分数降序排序后真实检查:分数相同的几个人内部顺序,真实还是插入时的字母序,证明 Arrays.sort(Object[]) 用的 TimSort 是真实稳定的排序。对照组用 int[] 存同样的分数排序,排序结果里的重复值真实没有任何身份信息,证明原始类型数组的排序根本谈不上稳定性这个概念。
真实运行三个方法验证 finally 和 return 的交互:try 里 return 1、finally 里 return 2,真实返回值是 2,try 的返回值被真实覆盖;try 里抛出一个 RuntimeException、finally 里也 return,真实结果是异常被完全吞掉,调用方拿到的是一个看起来正常的返回值,完全不知道曾经抛过异常;对照组 finally 里只是修改局部变量不 return,真实不影响已经确定要返回的值。
真实验证两次 Color.values() 调用返回的数组用 == 比较是 false,说明每次都是真实新克隆的;修改一次拿到的数组不会影响下一次调用的结果;真实计时 2000 万次循环,每次都调用 values() 比缓存后复用同一个数组慢了约 2 倍,两次独立运行结果一致(1.9 倍和 2.0 倍)。for (Color c : Color.values()) 这种常见写法在热路径循环里会真实产生大量不必要的数组克隆。
真实计时对比 AtomicLong 和 LongAdder 在 1、4、16 线程下各自自增 400 万次的耗时:单线程无竞争时 LongAdder 反而比 AtomicLong 略慢(0.8~0.9 倍);4 线程时 LongAdder 真实快 17~21 倍;16 线程时真实快 40~55 倍,两次独立运行趋势一致。LongAdder 靠把热点计数器拆成多个内部 Cell 分散 CAS 竞争,读 sum() 时才真实累加所有 Cell,牺牲了实时精确性换来了真实的高并发吞吐量。
迭代开始后真实往 CopyOnWriteArrayList 里塞两个新元素,迭代器真实只看到原来的 5 个,没有抛出 ConcurrentModificationException;对照普通 ArrayList 同样操作真实抛出异常。另一组真实计时测出 add 单次耗时随当前长度增长:size≈100 时约 627ns,size≈10000 时涨到约 7694ns——每次写操作都要真实把底层数组整个复制一份,这是快照语义的真实代价。
用 accessOrder=true 构造 LinkedHashMap,重写 removeEldestEntry 做一个容量为 3 的缓存:真实验证插入 1,2,3 后迭代顺序是 [1,2,3],访问 get(1) 之后迭代顺序真实变成 [2,3,1],再插入第 4 个,真实淘汰的是 2(最久没被访问的),不是 1(最早插入的)。第二组实验用连续访问制造更明确的顺序,真实验证淘汰结果和预期完全一致。
放 10000 个没有其它强引用的 key 进 WeakHashMap,gc 之前 size() 真实是 10000;触发两次 System.gc() 之后,第一次调用 size() 就真实变成了 0,不需要额外调用任何清理方法;对照组里故意保留 200 个 key 的强引用,GC 之后 size() 真实剩下正好 200 个,一个不多一个不少。
在 finalize() 里把 this 重新赋给一个静态字段,真实验证对象复活:resurrected 字段真实变成非 null,对象确实活了过来;但同时创建的 WeakReference 真实测出已经被清空,拿不到对象了,因为 GC 清理弱引用发生在运行 finalize 之前,弱引用一旦被清空就不会因为后来的复活而恢复;再对照 Cleaner,清理动作根本拿不到 this,真实验证没有复活的可能。
搭一个容量为 1 的有界缓冲区,4 个生产者 + 4 个消费者共享同一把锁,共 8 万个任务;用 notify() 真实跑 3 次,3 次全部在 5 秒超时内真实卡死,只完成 2、2、4 个;换成 notifyAll(),3 次全部真实在 1 秒内完成全部 8 万个——notify() 唤醒了错误类型的线程之后,双方就永久卡在 wait() 上,这就是真实的丢失唤醒。
真实计时对比 synchronized 的两种场景:单线程无竞争连续 4000 万次自增,和 8 线程竞争同一把锁共 4000 万次自增;真实测出无竞争约 215ms,8 线程竞争约 2084ms,倍数约 9.7;两次独立运行倍数分别是 9.69 和 8.74,结果 count 都正确,没有丢失更新——真实代价主要来自线程阻塞、唤醒和上下文切换。
用 javap -c 真实反编译一段 a+b+c 的字节码,发现 Java 9 之后编译器根本不生成 new StringBuilder,而是生成一条 invokedynamic 调用 makeConcatWithConstants;循环里 s = s + i 的每一次迭代也是独立的 invokedynamic 调用,真实计时测出比显式 StringBuilder.append 慢了约 400 倍,所以别在循环里用 + 这条建议仍然成立,只是原因变了。