Java多线程同步锁场景下Thread.sleep(1)导致执行时间异常的原因排查
嘿,这个问题我之前也踩过一模一样的坑!核心原因不是你的锁用错了,也不是JVM版本的锅,而是Thread.sleep(1)的实际睡眠时间根本不是精确的1毫秒,再加上操作系统线程调度的额外开销,直接把总耗时给放大了。
我来给你拆解清楚:
1. Thread.sleep(1)的“小谎言”
你以为调用Thread.sleep(1)线程就会精确休眠1ms?其实完全不是!这个方法的语义是“让线程至少休眠指定的毫秒数”,但实际休眠时间取决于操作系统的时钟中断粒度:
- 比如Windows系统默认的时钟粒度是15.6ms左右(老版本甚至更高),Linux系统默认可能是1ms,但也会有波动;
- 当你调用sleep(1)时,线程会被挂起,直到下一次时钟中断到来才会被唤醒,所以实际休眠时间往往是操作系统时钟粒度的整数倍,远大于你指定的1ms。
你可以单独跑一段循环调用Thread.sleep(1)的代码,统计1000次的总耗时,肯定会发现结果远大于1000ms。
2. 结合你的代码分析耗时差异
看你的代码,每个线程的process方法会循环1000次,每次调用stage1()和stage2(),每个方法里都有一次Thread.sleep(1)——也就是说每个线程要执行2000次sleep操作。
假设你的系统每次sleep实际耗时2ms(已经是比较乐观的情况),单线程执行总耗时大概是2000*2=4000ms(4秒)。那你用了两个线程,按道理应该可以并行执行(因为stage1用lock1,stage2用lock2,两个线程的stage1和stage2可以同时跑),为什么总耗时还是4秒?
这里还有个容易忽略的细节:你的两个线程共享同一个Random实例!Random.nextInt()方法虽然线程安全,但它内部是用CAS操作实现的,高并发下会有竞争开销,导致两个线程的执行互相拖慢,没办法完全并行。
再看你补充的循环1次的情况:总耗时6~8ms,这也完全符合我们的分析——两次sleep的实际耗时加起来,再加上线程启动、调度的额外开销,远大于理论上的2ms。
3. 为什么教程里只需要2秒?
大概率是教程的测试环境和你的不一样:
- 教程用的操作系统时钟粒度更细(比如Linux系统开启了高精度时钟,或者修改了调度参数),sleep(1)的实际耗时更接近1ms;
- 或者教程里的代码没有共享
Random实例,两个线程可以完全并行,总耗时接近单线程的一半; - 甚至有可能教程里的sleep只是模拟耗时,实际用的是自旋等待这类更精确的方式,而不是真正的Thread.sleep。
验证方法
你可以做几个小实验来验证我的说法:
- 把
Random实例改成每个线程单独持有,看看总耗时会不会降低; - 把
Thread.sleep(1)换成LockSupport.parkNanos(1000000)(1ms对应的纳秒数),看看实际耗时会不会更接近预期; - 在Linux系统上运行代码,对比Windows下的耗时差异。
备注:内容来源于stack exchange,提问作者mohamedesam

