锁销除
java中的锁销除是指JVM即时编译器在运行的时候,对一些代码要求同步、但是对被检测到不可能存在共享数据的竞争的锁进行消除。锁销除的主要的依据来源于逃逸机制分析的数据支持、如果判断到一段代码中、在堆上的所有的数据都不会逃逸出去被其它的线程访问到的时候,那么就可以把他们当作栈上的数据对待。栈是线程私有的,也就不会发生线程不安全的问题了。
来看:👇
这是看起来一段没有锁的代码
但是在JDK5之前字符串的加法操作会转换为StringBuilder对象连续的append()方法来操作、JDK5之后会转换为StringBuffer对象连续的append()方法来操作。StringBuilder和StringBuffer的append()方法区别😄:
来看源码:
StringBuilder调用的是父类的append方法:
它的父类是AbstractStringBuilder
来看父类中的append()方法:
首先StringBuffer也是继承了AbstractStringBuilder类
StringBuffer重写了父类AbstractStringBuilder的append()方法:👇
将append()方法变为同步的了。
上图的代码最后会转变为下面的操作:
使用了synchronized关键来对方法的整体加锁。
在StringBuffer中都有一个同步块、锁的就是StringBuffer对象。虚拟机观察stringBuffer、在经过逃逸分析的时候发现它的动态作用域被限制在concatString()方法的内部。也就是说stringBuffer的所有的引用都不会逃到concatStrng()方法的外部、那也就是其它的线程无法访问到大、所以虽然这里有锁、但是可以被安全的消除掉。在解释执行的时候、这里任然会被加锁、但是经过服务器编译器的即时编译之后、这段代码就会忽略所有的同步的措施而直接执行。
锁粗化
写代码的时候、原则上会将锁的同步代码块的作用的范围限制的尽量的小、只有在共享数据的时候在实际的作用域中才进行同步、目的就是为了减少同步的操作的范围、使程序运行的效率变高。
在大多数的情况之下、这里的原则是正确的。但是如果一系列连续的操作都是对一个对象反复的加锁和解锁、比如加锁是在循环体之中的、那么就是没有线程的竞争、频繁的创建和销毁锁也会导致不必要的性能开销。
比如上面的方法、三个操作都是加锁的操作、那么我们也可以使用一把锁将三个append()方法加锁。
顺提 一下StringBuffer和StringBuilder的区别:
StringBuilder中的方法:
StringBuilder大多调用的是父类AbstractStringBuilder的方法
StringBuffer:
StringBuffer大多数的方法也是调用的是父类AbstractStringBuilder的方法、但是区别也是显而易见的、StringBuffer的调用父类方法的时候、大都被synchronized关键字修饰、也就是常说的StringBuffer是线程安全的。