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