synchronized锁的优化
基于对象头的锁状态对synchronized锁进行优化
基于锁的状态
从低到高依次为无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态【锁只能升级不能降级】
无锁状态
偏向锁
为什么要有偏向锁?
大多数的情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得锁,为了让线程获得锁的代价更低就引入了偏向锁。
偏向锁:从始至终只有一个线程请求某一把锁。同一个对象多次加锁(重入)
获取偏向锁
当一个线程访问同步代码块获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步代码块的时候不需要进行CAS操作来进行加锁和解锁,只需要测试一下对象头的Mark Word 里是否存储着指向当前线程的偏向锁。
如果成功,表示线程已经获得了锁;
如果失败,需要测试MarkWord中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS操作竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
撤销偏向锁
偏向锁使用了一种等到竞争出现才释放锁的机制,所以只有当其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放偏向锁。
关闭偏向锁
在JDK6之后是默认启动的。
如果确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,程序默认就会进入轻量级锁状态。
轻量级锁
多个线程在不同的时间段请求同一把锁,没有锁竞争,针对这种情况JVM采用了轻量级锁,来避免线程的阻塞以及唤醒。
原理:基于CAS实现,同一个时间点,经常只有一个线程竞争锁
重量级锁
悲观锁的情况
基于系统mutex锁,同一个时间点,经常有多个线程竞争
特点:mutex是系统级别加锁,线程会友用户态切换到内核态,切换成本较高(一个线程总是竞争失败,就会不停的在用户态和内核态切换,比较耗费资源;如果有很多个竞争失败的线程,性能就会有很大的影响)
锁粗化
将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成为一个范围更大的锁。即多个synchronized连续执行加锁、释放锁可以合并为一个。
示例:StringBuffer静态变量在一个线程多次append操作
静态变量属于方法区,JDK1.8之后在堆里面,是线程共享的,存在线程安全问题
public class Test{
private static StringBuffer sb = new StringBuffer();
public static void main(String[] args){
sb.append("a");
sb.append("b");
sb.append("c");
}
}
每次调用append方法都需要进行加锁和解锁。
JVM检测到有一系列连续的对同一个对象加锁和解锁的操作,就会及那个其合并成一次加锁和解锁的过程。
在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。
锁消除
删除不必要的加锁操作。不会逃逸到其他线程的变量,可以认为是线程安全的,执行加锁操作,可以删除加锁。
示例:StringBuffer局部变量,在一个线程多次append操作
局部变量属于虚拟机栈,是线程私有的
public class Test{
public static void main(String[] args){
StringBuffer sb = new StringBuffer();
sb.append("a").append("b").append("c");
}
}
StringBuffer的append方法是一个同步方法,但是在上面的代码中StringBuffer属于局部变量,所以该过程是安全的,完全可以不用加锁,所以可以将锁消除。