"synchronized 现在有锁升级,无竞争时很快"是常见说法,但"很快"和"有竞争时慢多少"很少有人给出真实数字。用同一个自增计数器,分别在单线程无竞争和 8 线程竞争下真实计时。
上一篇:字符串 + 拼接编译成了什么:真实反汇编发现不是 new StringBuilder
对照系列里 ReentrantLock 公平锁那篇的做法,这次换成最基础的 synchronized 关键字,看无竞争和有竞争之间的真实差距。
第一组:单线程自己反复进出 synchronized 块 4000 万次,没有其它线程抢这把锁。第二组:8 个线程一起抢同一把锁,总共也做 4000 万次自增,用 CountDownLatch 让所有线程同时起跑,保证竞争是真实、同时发生的:
SyncInflation.java(竞争场景核心) for (int i = 0; i < threads; i++) { ts[i] = new Thread(() -> { startGate.await(); // 所有线程一起起跑 for (int j = 0; j < perThread; j++) { synchronized (lock) { counter++; } } doneGate.countDown(); }); ts[i].start(); }
两组总操作次数完全相同(都是 4000 万次),唯一变量是有没有其它线程在抢同一把锁。
单线程无竞争: 40000000 次 synchronized 自增, 耗时 = 215ms, counter=40000000 8 线程竞争同一把锁: 共 40000000 次 synchronized 自增, 耗时 = 2084ms, counter=40000000 倍数(竞争/无竞争) = 9.69
单线程无竞争: 40000000 次 synchronized 自增, 耗时 = 243ms, counter=40000000 8 线程竞争同一把锁: 共 40000000 次 synchronized 自增, 耗时 = 2127ms, counter=40000000 倍数(竞争/无竞争) = 8.74
两次运行的倍数分别是 9.69 和 8.74,量级稳定在 9 倍左右。两组的 counter 最终都真实等于 40000000,证明 synchronized 全程保证了互斥,没有丢失更新——这也是系列前面volatile 那篇里普通 int 和 AtomicInteger 对照实验的反面:这里用了真实的互斥锁,竞态条件被完全挡住了,代价就是这 9 倍的真实性能差距。
每一行都是真实运行这段代码,通过 Thread.getState() 和 ThreadMXBean 得到的结果。