原文:Java 锁消除和锁粗化
锁粗化
通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的请求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,即使单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。
一种极端的情况如下:
public void doSomethingMethod(){
synchronized(lock){
// do some thing
}
// 这是还有一些代码,做其它不需要同步的工作,但能很快执行完毕
synchronized(lock){
// do other thing
}
}
上面的代码是有两块需要同步操作的,但在这两块需要同步操作的代码之间,需要做一些其它的工作,而这些工作只会花费很少的时间,那么我们就可以把这些工作代码放入锁内,将两个同步代码块合并成一个,以降低多次锁请求、同步、释放带来的系统性能消耗,合并后的代码如下:
public void doSomethingMethod(){
// 进行锁粗化:整合成一次锁请求、同步、释放
synchronized(lock){
// do some thing
// 做其它不需要同步但能很快执行完的工作
// do other thing
}
}
注意:这样做是有前提的,就是中间不需要同步的代码能够很快速地完成,如果不需要同步的代码需要花很长时间,就会导致同步块的执行需要花费很长的时间,这样做也就不合理了。
另一种需要锁粗化的极端的情况是:
for(int i = 0; i < size; i++){
synchronized(lock){
}
}
上面代码每次循环都会进行锁的请求、同步与释放,看起来貌似没什么问题,且在 JDK 内部会对这类代码锁的请求做一些优化,但是还不如把加锁代码写在循环体的外面,这样一次锁的请求就可以达到我们的要求,除非有特殊的需要:循环需要花很长时间,但其它线程等不起,要给它们执行的机会。
锁粗化后的代码如下:
synchronized(lock){
for(int i = 0; i < size; i++){
}
}
锁消除
锁消除是发生在编译器级别的一种锁优化方式。有时候我们写的代码完全不需要加锁,却执行了加锁操作。
比如,StringBuffer 类的 append 操作:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
从源码中可以看出,append 方法用了 synchronized 关键词,它是线程安全的。但我们可能仅在线程内部把 StringBuffer 当作局部变量使用:
public class Demo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
int size = 10000;
for (int i = 0; i < size; i++) {
createStringBuffer("Hello", "Word");
}
long timeCost = System.currentTimeMillis() - start;
System.out.println("createStringBuffer:" + timeCost + " ms");
}
public static String createStringBuffer(String str1, String str2) {
StringBuffer sBuf = new StringBuffer();
sBuf.append(str1); // append 方法是同步操作
sBuf.append(str2);
return sBuf.toString();
}
}
代码中 createStringBuffer 方法中的局部对象 sBuf,只在该方法内的作用域有效,不同线程同时调用 createStringBuffer() 方法时,都会创建不同的 sBuf 对象,因此此时的 append 操作若是使用同步操作,就是白白浪费的系统资源。
这时我们可以通过编译器将其优化,将锁消除,前提是 Java 必须运行在 server 模式(server 模式会比 client 模式作更多的优化),同时必须开启逃逸分析:
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
其中+DoEscapeAnalysis
表示开启逃逸分析,+EliminateLocks
表示锁消除。
标签:同步,Java,synchronized,代码,sBuf,粗化,消除,append From: https://www.cnblogs.com/Higurashi-kagome/p/18278154逃逸分析:比如上面的代码,它要看 sBuf 是否可能逃出它的作用域?如果将 sBuf 作为方法的返回值进行返回,那么它在方法外部可能被当作一个全局对象使用,就有可能发生线程安全问题,这时就可以说 sBuf 这个对象发生逃逸了,因而不应将 append 操作的锁消除,但我们上面的代码没有发生对象逃逸,锁消除就可以带来一定的性能提升。