前阵子调一个老服务,日志里隔三差五蹦空指针,本地复现不了,上了线才炸。第一反应肯定是并发嘛,翻代码一看相关方法都标了 synchronized,当时还挺得意,心想这下稳了。结果坑就在这"以为稳了"上。
最阴的是有个单例 new 了两个实例出来,每个实例各自管自己的锁,等于谁也没锁住谁,多线程进去各玩各的。还有个把 ArrayList 当共享缓存使的地方,读的时候遍历、写的时候 add,多线程一搅和直接 ConcurrentModificationException,堆栈还特别迷惑。最绝的是早年写的 double-checked locking,光顾着性能把 volatile 省了,对象半初始化就被另一个线程读出去,偶尔返回一个字段全空的对象,空指针查到怀疑人生。
说白了并发的坑真不在"没加锁",是"你以为加对了"。锁加在谁的监视器上、共享状态到底有没有被同一把锁罩住、可见性保没保证,这些不捋清,加一百把锁也是心理安慰。现在但凡碰共享状态我都先想清楚到底哪些线程会碰它,比事后抓狂省事多了。