锁消除和锁粗化都是jvm对于锁的优化措施
锁消除
锁消除就是字面意思,虚拟机会根据自己的代码检测结果取消一些加锁逻辑。虚拟机通过检测会发现一些代码中不可能出现数据竞争,但是代码中又有加锁逻辑,为了提高性能,就消除这些锁。如果一段代码中,在堆上的所有数据都不会被其他线程访问到,那就可以把它们当成线程私有数据,自然就不需要同步加锁了。
最典型的例子就是多个String变量连接时的代码:
String s1 = "hello";
String s2 = "world";
String s3 = s1+s2;
经过编译器优化后,这段代码就变成了下面的样子:
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("world");
s3 = sb.toString();
因为我们知道append方法时一个同步方法,它的方法签名是:
public synchronized StringBuffer append(String str);
这时候,编译器就会判断出sb这个对象并不会被这段代码块以外的地方访问到,更不会被其他线程访问到,这时候的加锁就是完全没必要的,编译器就会把这里的加锁代码消除掉,体现到java源码上就是把append
方法的synchronized
修饰符给去掉了。
锁粗化
原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小---只在共享数据的实际作用域内才进行同步,这样是为了使得需要同步的操作数量尽可能变少,即使存在锁竞争,等待锁的线程也能进可能快地拿到锁。
大多数情况下,上面的原则都是对的,但是如果一系列的连续操作都对同一个对象反复加锁解锁,甚至加锁操作时出现在循环体中,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
看以下代码:
public void method(String s1, String s2){
synchronized(this){
System.out.println("s1");
}
synchronized(this){
System.out.println("s1");
}
}
编译成字节码后的样子如下,注意其中写着作者注
的地方
0: aload_0
1: dup
2: astore_3
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String s1
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_3
13: monitorexit // 作者注:这行代码会被jvm去掉
// 作者注:这里省略了不关注的代码
20: monitorexit // // 作者注:这行代码会被jvm去掉
21: aload 4
23: athrow
24: aload_0
25: dup
26: astore_3
27: monitorenter
28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc #3 // String s1
33: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: aload_3
37: monitorexit
38: goto 48
41: astore 5
43: aload_3
44: monitorexit
45: aload 5
47: athrow
48: return
其实也就是想到与将代码优化成了这样子:
public void method(String s1, String s2){
synchronized(this){
System.out.println("s1");
// }
// synchronized(this){
System.out.println("s1");
}
}
网友评论