锁消除
自旋锁在JDK 1.6中默认开启。自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程会白白浪费处理器资源,反而带来性能上的浪费。因此,自旋等待的时间必须有一定的限度,如果自旋超过了限定的次数仍然没有成功获取锁,就应该使用传统的方式来挂起线程了。
在JDK 1.6中引入了自适应的自旋锁,自适应意味着自选的时间不再固定了,而是由上一次在同一个锁上自旋时间以及锁的拥有者的状态来决定。
锁消除
锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
例如,如下代码(看起来没有同步的代码)
public String concatString(String s1,String s2,String s3){
return s1+s2+s3;
}
我们知道,由于String是一个不可变的类,对字符串的连接操作总是通过生成新的String对象来进行的,因此Javac编译器会对String连接做自动优化。在JDK 1.5之后,会转化成StringBuffer对象的连续append()操作。
public String concatString(String s1,String s2,String s3){
StringBuffer sb=new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
现在大家还认为这段代码没有同步吗?每个StringBuffer.append()方法都有一个同步块,锁就是sb对象。虚拟机观察变量sb,很快就会发现它的动态作用域限制在concatString()方法内部,其他线程无法访问它,因此虽然这里有锁,但是可以被完全的消除掉,在 即时编译后,这段代码就会忽略掉所有的同步而直接执行了。
锁粗化
原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。
大部分情况下,上面的原则是对的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作时出现在循环体中,那及时没有锁竞争,频繁的进行互斥同步操作也会导致不必要的性能损耗。
连续的StringBuffer.append()方法就属于这类情况,如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步不范围(粗化)到整个操作序列的外部(例如,第一个append()操作之前知道最后一个appen()操作之后,这样只需要加锁一次就可以了)。
- 点赞
- 收藏
- 关注作者
评论(0)