首页 > 代码库 > 《深入理解Java虚拟机》笔记04 -- 并发锁
《深入理解Java虚拟机》笔记04 -- 并发锁
Java虚拟机在操作系统层面会先尽一切可能在虚拟机层面上解决竞争关系,尽可能避免真实的竞争发生。同时,在竞争不激烈的场合,也会试图消除不必要的竞争。实现这些手段的方法包括:偏向锁、轻量级锁、自旋锁、锁消除、锁膨胀等
1. 偏向锁
偏向锁是JDK1.6提出的一种锁优化方式。其核心思想是:如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,无需再进行相关的同步操作,从而节省了操作时间。如果在此之间有其他线程进行了锁请求,则锁退出偏向模式。在JVM中使用-XX:+UseBiasedLocking 可以设置启用偏向锁。
偏向锁在锁竞争激烈的场合没有太强的优化效果。
2. 轻量级锁
如果偏向锁失败,Java虚拟机会让线程申请轻量级锁。轻量级锁在虚拟机内部,使用一个称为BasicObjecLock的对象实现,这个对象内部由一个BasicLock对象和一个持有该锁的Java对象指针组成。BasicobjectLock对象放置在Java栈的栈帧中。
首先,BasicLock通过set_displaced_header()方法备份了原对象的Mark Word。接着,使用CAS操作,尝试将BasicLock的地址复制到对象头的Mark Word。如果复制 成功,那么加锁成功。如果加锁失败,那么轻量级锁就有可能被膨胀为重量级锁。
3. 锁膨胀
当轻量级锁失败时,就会膨胀成重量级锁。轻量级锁加锁失败后,虚拟机会执行以下操作:1. 废弃前面BasicLock备份的对象头信息。2. 通过inflate()方法进行锁膨胀,其目地是获得对象的ObjectMonitor 3. 使用enter()方法尝试进入锁。
4. 自旋锁
锁膨胀后,进入ObjectMonitor的enter(),纯种很可能会在操作系统层面被挂起,这样线程上下文切换的性能损失就比较大。因此,在锁膨胀之后,虚拟机会做最后的争取,希望线程可以尽快进入临界区而避免被操作系统挂起。一种较为有效的手段就是使用自旋锁。
自旋锁可以使线程在没有取得锁时,不被挂起,而转去执行一个空循环(即所谓的自旋),在若十个空循环后,线程如果可以获得锁,则继续招待。若依然不能获得锁,才会被挂起。不太适用于锁竞争激烈的场合。
在JDK 1.6中,Java虚拟机提供 -XX:+UseSpinning 参数来开户自旋锁,使用 -XX:PreBlockSpin 参数来设置自旋锁的等待次数。
在JDK1.7中,自旋锁参数被取消。自旋锁总是会执行,自旋次数也由虚拟机自行调整。
5. 锁消除
锁消除是Java虚拟机在JIT编译时,对过对运行上下文的扫描,运用逃逸技术,去除不可能存在共享资源竞争的锁。比如:
public void someBusiness(){ ...... StringBuffer sb = new StringBuffer(); sb.append("XXX"); ...... }
StringBuffer中的方法都是有synchronized的,但是这里只是方法内的局部变量,不可能存在锁竞争。
逃逸分析和锁消除分别可以使用参数 -XX:+DoEscapeAnalysis 和 -XX:+EliminateLocks 开启(锁消除必须工作在 -server模式下)
《深入理解Java虚拟机》笔记04 -- 并发锁