参考文章为:
https://www.cnblogs.com/pureEve/p/6421273.html
https://www.jianshu.com/p/9932047a89be (jvm锁降级)
http://www.cnblogs.com/charlesblc/p/5994162.html (详解)
monitorenter、montitorexit
线程获取锁的状态转换:
1. ContentionList:请求锁的线程竞争队列(阻塞队列)
2. EntryList:满足要求的队列(阻塞队列)
3. OnDeck:任一时刻仅能有一个线程去竞争锁
4. 锁拥有者线程
5. 调用wait的阻塞队列(阻塞队列)
描述:
方法或代码块在运行时,保证同一时刻只有一个线程进入该代码。
下面反编译一个代码看看他的机制:
class Sync {
public synchronized void test(){
System.out.println("sync test func");
}
public void test2(){
synchronized (this){
System.out.println("sync test block");
}
}
}
public class Test {
public static void main(String[] args) {
Sync sync = new Sync();
sync.test();
sync.test2();
}
}
然后执行如下指令反编译并查看结果:
$ javac Test.java
$ javap -verbose Sync
从上面可看出 同步方法使用方法修饰符ACC_SYNCHRONIZED标识,
同步代码块使用monitorenter、monitorexit来获取释放锁。monitorenter插入到同步代码块开始处,monitorexit插入到同步代码块结束处。
前提:java对象头和monitor是synchronized基础,以下讲解
Java对象头:
Java对象头一般占两个机器码(32位虚拟机中,1个机器码=4字节=32bit,如对象是数组则需多一机器码记录长度),以下是32位对象头存储结构:
上图为Mark Word的结果图。
synchronized用的锁存在Java对象头中,包含"标记字段Mark Word"、"类型指针Klass Pointer"分别如上图区域部分。其中标记字段存储对象自身运行时数据,实现轻量级锁和偏向锁的关键。类型指针确定是哪个类的实例。MarkWord被设置为对象的HashCode,其低三位表示MarkWord状态,初始无锁状态下为001。
Monitor:
每个对象均会带有一个Monitor或对象锁,Monitor是线程私有的数据结构。每个线程包含(可用Monitor Record列表、全局可用列表),每个被锁住的对象都会和一个monitor关联。
注:Owner标识该锁被对应标识的线程占用。
Java对象头有四种状态的锁(synchronized用的锁存在Java对象头中):
前提:1. Java SE1.6锁共四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状。
2. 锁只能升级(锁膨胀),不能降级(频繁锁升降级会影响性能)。
什么是自旋锁:
在线程进入阻塞之前,可采取自旋的策略,占着cpu不放,这是为了避免线程被阻塞后进入内核调度,导致内核与用户态频繁而切换印象锁性能。最理想的自旋周期是一个CPU上下文切换时间。
什么是偏向锁:
HotSpot作者大多数情况不存在多线程竞争,导致锁由同一个线程多次获得,耗费资源。为了降低锁获取代价,引入偏向锁。偏向锁会在线程访问时,在Java对象头锁记录中存储锁偏向的线程ID。在当前线程加锁或解锁情况下,测试对象头Mark Word是否含指向当前线程的偏向锁。测试成功表当前线程已获得锁,测试失败则首先确认Mark Word中偏向锁标识是否设置为1,是则尝试用CAS将对象头偏向锁指向当前线程。否则用CAS竞争。
偏向锁撤销:有竞争时,当前偏向锁就释放掉。但需要等待全局安全点(当前时间点无字节码在执行)。撤销过程首先暂停所有拥有偏向锁线程,尝试直接切换。失败则继续运行,标记对象不适合偏向锁,锁膨胀(轻量级锁)。
偏向锁在java6\7默认启用,并在应用程序启动延迟几秒后激活,可用-XX:BiasedLockingStartupDelay = 0关闭延迟。用-XX:-UseBiasedLocking=false 关闭偏向锁。
什么是轻量级锁:
轻量级加锁:线程在执行同步之前,jvm在当前线程创建用于存储锁记录空间,并将Mark Word复制到所记录中,称"Displaced Mark Word"。然后线程尝试用CAS将Mark Word替换为指向锁记录指针。成功表示无竞争,失败则存在竞争,当前线程尝试使用自旋来获取锁,如果自旋获取失败则锁膨胀升级为重量级锁。
轻量级解锁:原子性CAS操作将"Displaced Mark Word"替换回对象头,成功表没竞争,失败表存在竞争,锁膨胀为重量锁。
锁升级的总过程:
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,
之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,
把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord,
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。
总结:偏向锁适合于单线程请求环境,如果线程一般就一个可采用偏向锁,并且取消其延时提高效率。如果一般在线程竞争环境下即可直接取消偏向锁,提高性能。锁只会升级不会降低,避免锁切换带来的额外性能消耗。