文章目录
线程上下文切不会引起系统调用,但是开销也很大;
JVM中的锁
锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态;
Java对象头Mark Word
这里重点关注对象头中的Mark Word。
MarkWord里默认数据是存储对象的HashCode等信息,但是在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。
重量级锁也就是通常说synchronized的对象锁,锁标识位为10。
其中 栈中锁记录 是指lock record;
锁的四个状态:无锁状态(unlocked)、偏向锁状态(biasble)、轻量级锁状态(lightweight locked)和重量级锁状态(inflated)。
1. 偏向锁
当一个线程访问同步块并成功获取锁时,会在 对象头 的mark word 和 栈帧中的锁记录 里存储这个的线程ID。
定义
大多数情况下,锁不存在多线程竞争,而且总是由同一线程多次获得;
偏向锁 是为了消除无竞争情况下的同步操作,进一步提升程序性能。
https://www.cnblogs.com/linghu-java/p/8944784.html
偏向锁的获取过程
当一个线程访问同步块并成功获取锁时,会在 对象头 的mark word 和 栈帧中的锁记录 里存储这个的线程ID。
当一个线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着当前线程的;
如果测试成功,表示该线程已经获得了锁。
如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否是1(1表示当前锁是偏向锁);
如果不是1,则当前锁不是偏向锁,则使用CAS竞争锁;
如果是1,则当前锁是偏向锁,则尝试使用CAS 将对象头的偏向锁指向当前线程;
注意,一般 的锁,在加锁成功后,会在 对象头 的mark word 和 栈帧中的锁记录 里存储这个的线程ID。但是偏向锁加锁时,只需要对 对象头做操作,不会多对栈帧锁记录做操作;
偏向锁的撤销
首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着 ;
如果线程处于不活动状态,则将对象头设置成无锁状态;
如果线程仍然活着,拥有偏向锁的栈会被执行,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁状态
或者 标记对象不适合作为偏向锁,最后唤醒暂停的线程
开启偏向锁
偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活(延迟激活)。
可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。
如果应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-
UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。
2. 轻量级锁
如上文 所叙,jvm默认开启偏向锁,当关闭偏向锁时,那么默认进入轻量级锁状态;
https://www.cnblogs.com/deltadeblog/p/9559035.html
https://blog.csdn.net/qq_35124535/article/details/70312553
线程在执行同步块之前,JVM会先在 当前 线程 的 栈桢 中 创建用于存储锁记录的空间(lock record),并将对象头中的Mark Word复制到锁记录中。
然后 线程尝试使用 CAS 将对象头的Mark Word中的锁指针 指向锁记录lock record。
如果成功,当前线程获得锁(并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态),如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋(循环等待)来获取锁。
轻量级锁解锁
使用CAS操作将 Displaced Mark Word(锁记录 lock record) 替换回到对象头;
如果成功,则说明没有发生竞争
失败,则表示当前锁存在竞争,锁就会膨胀成重量级锁;
3. 重量级锁
其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程,进行竞争。
锁对比
总结:
偏向锁 只适用于只有一个线程访问同步代码块的情形,一旦遇到竞争,就会升级为轻量级锁;
可以理解为偏向锁不存在竞争;
轻量级锁 存在竞争,但是竞争失败的线程 不会被阻塞,而是通过 自旋 等待再次获取锁;但是自旋消耗cpu;
而 重量级锁,竞争失败的线程汇会被阻塞;
锁升级
无锁 → 偏向锁
无锁 升级 为 偏向锁 就是 偏向锁 第一次获取锁的过程;
访问同步代码时,对象头没有存储线程id,直接使用cas将mark word 线程id 改为当前线程id;
偏向锁 -> 轻量级锁
当有第二个线程竞争锁时,则升级为轻量级锁
轻量级锁 → 重量级锁
轻量级锁 发生竞争时需要通过自旋 等待获取锁,但是如果自旋的时间太长也不行,因此自旋的次数是有限制的,如果自旋次数到了线程还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。
重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
总结:
- 当有另一个线程与该线程同时竞争时,自旋到一定次数还未获取到锁,锁会升级为重量级锁。
- 有多个线程(大于2)竞争锁;