synchronized锁优化的背景
- 用锁能够实现数据的安全性,但是会带来性能下降。
- 无锁能够基于现成并行提升程序性能,但是会带来安全性下降
升级过程
由对象头重的Mark Word根据锁标志位的不同而被复用及锁升级策略
java5以前,只有Synchronized,这个是操作系统级别的重量级操作
重量级锁,假如锁的竞争比较激烈的话,性能下降。
synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的来实现的,挂起线程和恢复线程都需要转入内核态去完成,用户态和内核态频繁切换,为了减少获得锁和释放锁所带来的性能消耗,java6引入了轻量级锁和偏向锁
Synchronized锁种类及升级步骤
synchronized用的锁是存在Java对象头里的Mark Word中,锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
锁指向
-
偏向锁:MarkWord存储的是偏向的线程ID;
-
轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针;
-
重量锁:MarkWord存储的是指向堆中的monitor对象的指针;
无锁状态
倒着看25位全0 是无锁
31位是hashcode二进制 需要调用hashcode()才会有值
偏向锁
在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。
那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接会去检查锁的MarkWord里面是不是放的自己的线程ID)。
如果相等,表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。
以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
如果不等,表示发生了竞争,锁己经不是总是偏向于同一个线程了,这个时候会尝试使用CAS来替换MarkWord里面的线程ID为新线程的ID,
- 竞争成功,表示之前的线程不存在了,MarkWord里面的线程1D为新线程的ID,锁不会升级,仍然为偏向锁;
- 竞争失败,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟,4秒
所以需要添加参数-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。
开启偏向锁:
-XX:+UseBiasedLocking
关闭偏向锁:关闭之后程序默认会直接进入 ----------------------->>>>>>> 轻量级锁状态
-XX:-UseBiasedLocking
偏向锁的撤销
偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。
-
当有另外线程逐步来竞争所的时候,就不能再使用偏向锁了,要升级为轻量级锁
-
竞争线程尝试CAS更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁
① 第一个线程正在执行synchronized方法(处于同步块),会把原获得偏向锁的线程升级为轻量级锁后继续执行同步代码块,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会 CAS 自旋来获得该轻量级锁,当自旋次数超过一定数量,升级为重量级锁。
②第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向。