重量级锁竞争的时候,还可以使用自旋来进行优化。
如果当前进程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞「阻塞需要上下文切换,降低速度」。
自旋重试成功
线程 1(CPU 1 上) | 对象 Mark | 线程2(CPU2 上) |
---|---|---|
- | 10(重量锁) | - |
访问同步块,获取 monitor | 10(重量锁) 重量锁指针 | - |
成功(加锁) | 10(重量锁) 重量锁指针 | - |
执行同步块 | 10(重量锁) 重量锁指针 | - |
执行同步块 | 10(重量锁) 重量锁指针 | 访问同步块,获取 monitor |
执行同步块 | 10(重量锁) 重量锁指针 | 自旋重试 |
执行完毕 | 10(重量锁) 重量锁指针 | 自旋重试 |
成功(解锁) | (无锁)) | 自旋重试 |
- | 10(重量锁) 重量锁指针 | 成功(加锁) |
- | 10(重量锁) 重量锁指针 | 执行同步块 |
- | … | … |
怎么理解上述含义呢?
- 线程 1 进行了同步块的加锁
- 线程 2 也想用同步块,但是,它没有看到有人加锁后,就进入阻塞阶段,而是,不断的询问锁是否释放
- 因为切换到阻塞,是切换上下文,消耗资源
- 如果锁释放了,则它进行同步块加锁
所以,自旋优化只有多核 CPU
的时候才有意义,单核 CPU
,都是顺序执行,老是重复问没有意义。
自旋重试失败
线程 1(CPU 1 上) | 对象 Mark | 线程2(CPU2 上) |
---|---|---|
- | 10(重量锁) | - |
访问同步块,获取 monitor | 10(重量锁) 重量锁指针 | - |
成功(加锁) | 10(重量锁) 重量锁指针 | - |
执行同步块 | 10(重量锁) 重量锁指针 | - |
执行同步块 | 10(重量锁) 重量锁指针 | 访问同步块,获取 monitor |
执行同步块 | 10(重量锁) 重量锁指针 | 自旋重试 |
执行完毕 | 10(重量锁) 重量锁指针 | 自旋重试 |
执行完毕 | 10(重量锁) 重量锁指针 | 自旋重试 |
执行完毕 | 10(重量锁) 重量锁指针 | 阻塞 |
- | … | … |
JAVA 6
之后,自旋锁是自适应的,比如对象刚刚一次自旋操作成功过,那么,认为这次自旋成功的可能性会高,就多旋几次;反之,就少自旋身之不自旋- 自旋会占用
CPU
时间,单核CPU
自旋就是浪费 JAVA7
之后不能控制是否开启自旋功能