锁升级的顺序:偏向锁-> 轻量级锁->重量级锁

偏向锁:
1、 使用了 synchronized关键字的代码,如果没有多线程竞争的话,线程锁为偏向锁,
偏向锁的实现:通过cas操作将线程id存储到对象头内,存储成功的话对象头内会获得线程 id和锁的标记(101),表示当前线程获得了偏向锁(仅第一次做cas操作,之后线程进入会去判断线程id,线程id相同,直接获得锁,若线程id不同则出现竞争)
2、 当两个线程存在竞争关系时,第一个线程拥有偏向锁时,第二个线程会去撤销线程一的锁, 撤销成功(有两种情况能成功,第一线程一直接结束,第二线程一到达了一个全局安全点 safepoint, synchroized源码会判定is_at_safepoint),线程二获得偏向锁,撤销失败,线程二进行锁升级,升级到轻量级锁
注意:如果要获取锁对象的hascode是无法使用偏向锁的(因为偏向锁的对象头只存了线程ID 23bit和Epoch2bit,无锁状态前25bit用来存储对象的hascode,会直接使用重量级锁,对象 hash 值会存在 ObjectMonitor _header markOop 对象头)中)

class markOopDesc: oopDesc {
」V”:•:
// Conversion uintptr_t valueO { (uintptr_t) this; }
public:
i__ // Constants .

};
延伸:每一个java对象最终都会转换为jvm虚拟机的一个instanceOopDesc对象,引用jvm instanceOopDesc 对象的一句注释:"// An instanceOop is an instance of a Java Class"
偏向锁的获得和撇销流程

轻量级锁: 会在线程栈帧中分配一个lock record空间存储线程对象头,线程对象头存储指向lock record 的指针,然后回通过cas操作,替换对象头的指针,替换成功当前线程获得轻量级锁,替换失 败,进行自旋(多次cas: 1.6以前10次,1.6以后自适应自旋,根据上一次自旋获得锁的时 间,决定当前自旋的次数),自旋失败,锁膨胀升级为重量级锁

重量级锁:
通过对象监视器monitor获取锁,monitorenter抢占锁成功获得锁,抢占失败也会自旋,自 旋失败则加入一个等待队列,等待获得锁的线程通过monitorexit释放锁,再唤醒阻塞队列里 的线程(是一个非公平锁)
死锁的条件(需全部满足):
1、 互斥,共享资源X和Y只能被一个线程占用;
2、 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X;
3、 不可抢占,其他线程不能强行抢占线程T1占有的资源;
4,循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环 等待。
解决死锁的方法:
1、 扩大锁的范围,一次性加载所有需要的对象(防止线程相互占用),统一做资源的加载和释放
2、 使用非阻塞的锁,比如ReentrantLock的tryLock()锁定对象
3、 按顺序加锁(获取对象的hashcode,比较hashcode值大小,然后加锁)
总结:synchroized锁升级是对锁优化的一个过程,偏向锁和轻量级锁实际是没有阻塞的,偏 向锁是通过一次cas操作去获取线程的执行,轻量级锁是通过多次cas,只有重量级锁,才会 阻塞等待
锁的升级的目的:
锁升级是为了减低了锁带来的性能消耗。在Java 6之后优化synchronized的实现方式,使 用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
1、 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一 个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些 CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。如果 在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的 偏向锁,将锁恢复到标准的轻量级锁。
2、 轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线 程加入锁争用的时候,轻量级锁就会升级为重量级锁;
3、 重量级锁是synchronized ,是Java虚拟机中最为基础的锁实现。在这种状态下,Java 虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。

Was this helpful?

0 / 0

发表回复 0

Your email address will not be published.