注意:在最终升级成重量级锁之前会进行自适应自旋
// 尝试获取锁 伪代码
while(!isLock()){
}
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
- 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
- 在 Java 6 之后自旋是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,比较智能。
- Java 7 之后不能控制是否开启自旋功能
注意:自旋的目的是为了减少线程挂起的次数,尽量避免直接挂起线程(挂起操作涉及系统调用,存在用户态和内核态切换,这才是重量级锁最大的开销)
锁粗化
锁粗化是指,将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,从而提升程序的执行效率。
锁粗化示例1:
package com.cctv;
/**
* Author:元哥说Java
* : szay2005
* : 1794803734
*/
public class LockTest1 {
public static void main(String[] args) {
LockTest1 lockTest1 = new LockTest1();
lockTest1.method();
}
public String method() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
synchronized (this){
sb.append( + i);
}
}
// synchronized (this) {
// for (int i = 0; i < 10; i++) {
// sb.append(+i);
// }
// }
return sb.toString();
}
}
如果在 for 循环中定义锁,那么锁的范围很小,但每次 for 循环都需要进行加锁和释放锁的操作,性能是很低的,但如果我们直接在 for 循环的外层加一把锁,那么对于同一个对象操作这段代码的性能就会提高很多
锁粗化示例2:
JVM检测到有一连串零碎的操作都是对同一对象的加锁,将会扩大加锁同步的范围(即锁粗化)到整个操作序列的外部
StringBuffer buffer = new StringBuffer();
/**
* 锁粗化
*/
public void append(){
buffer.append("aaa").append(" bbb").append(" ccc");
}
上述代码每次调用 buffer.append 方法都需要加锁和解锁,如果JVM检测到有一连串的对同一个对象加锁和解锁的操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。
锁消除
锁消除即删除不必要的加锁操作。锁消除是Java虚拟机在JIT编译期间,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。
锁消除的依据是逃逸分析的数据支持,如 StringBuffer 的 append() 方法,或 Vector 的 add() 方法,在很多情况下是可以进行锁消除的,比如以下这段代码:
package com.cctv;
public class LockTest2 {
public static void main(String[] args) {
LockTest2 lockTest1 = new LockTest2();
lockTest1.method();
}
public String method() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
sb.append("i:" + i);
}
return sb.toString();
}
}
以上代码经过编译之后的字节码如下:
从上述结果可以看出,之前我们写的线程安全的加锁的 StringBuffer 对象,在生成字节码之后就被替换成了不加锁不安全的 StringBuilder 对象了,原因是 StringBuffer 的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以此时我们就可以使用锁消除(不加锁)来加速程序的运行。