两个线程反向加锁(线程一先拿 lockA 再要 lockB,线程二先拿 lockB 再要 lockA),用 CountDownLatch 真实控制两边同时卡在互相等待的状态;不借助任何外部工具,只用 ThreadMXBean.findDeadlockedThreads(),真实检测到 2 个死锁线程,每个线程真实报告了它在等哪把锁、那把锁真实被谁占着——两个线程互为等待关系,真实、完整地闭合成一个环。
真实计时对比直接方法调用、未 setAccessible 的反射调用、已 setAccessible 的反射调用,各跑 5000 万次:直接调用约 32ms,反射调用(未 setAccessible)约 200ms,是直接调用的 6.2~6.5 倍;调用 setAccessible(true) 之后反射调用降到约 96~105ms,相对未调用时快了近 2 倍,但依然比直接调用慢约 3 倍。三组结果全程用求和结果校验一致,排除了任何计算错误的可能。
用两个独立的 URLClassLoader 加载同一份 Foo.class,真实验证:foo1 == foo2 是 false,两者是完全不同的 Class 对象;用其中一个 Class 创建的实例,真实无法强转成另一个的类型,抛出 ClassCastException: class Foo cannot be cast to class Foo——报错信息里两个 Foo 名字一模一样,区分它们的关键在括号里的类加载器信息。类在 JVM 里的身份真实是(全限定名, ClassLoader)这个二元组,不是名字本身。
真实运行 Integer.MAX_VALUE + 1,结果真实是 -2147483648,正好是 Integer.MIN_VALUE,没有抛出任何异常;用两个真实的大数 15 亿和 10 亿相加,int 版本真实得到一个负数,和真实数学结果完全对不上,程序对此毫不知情;对照用 Math.addExact 做同样的加法,真实抛出 ArithmeticException,不溢出时两者结果完全一致。long 也会溢出,只是数值范围大得多。
真实运行三个资源的 try-with-resources 验证关闭顺序:打开是 A、B、C,关闭真实是 C、B、A,和声明顺序正好相反;再让 try 主体和两个资源的 close() 都真实抛异常,catch 到的是 try 主体的异常,通过 getSuppressed() 真实拿到另外两个 close() 异常,顺序是先 B 后 A,没有一个异常真的丢失,只是被明确标记为次要的抑制异常挂在主异常下面。
写一个泛型可变参数方法 toArray(T... args) 把内部数组直接返回出去,再用它拼出经典的 pickTwo 场景:真实打印返回数组的运行时类型是 Object[],不是 String[];尝试把结果强转成 String[] 接收,真实抛出 ClassCastException。这不是编写者的疏忽,而是泛型可变参数在类型擦除下的真实、编译器拦不住的结构性缺口,SafeVarargs 注解只是承诺不做危险操作,不是消除风险。
真实验证 Collections.unmodifiableList(original) 不是深拷贝而是一层包装视图:original.add()/original.remove() 之后,拿到的 unmodifiable 引用真实同步跟着变了内容;直接对 unmodifiable 调用 add() 真实抛出 UnsupportedOperationException,证明它确实阻止了从这个引用发起的修改,但挡不住原始集合被修改;真正想要独立、不可变的快照,得先拷贝一份,再包装。
真实验证 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,牺牲了实时精确性换来了真实的高并发吞吐量。