java synchronized

 

一.Synchronized使用方法

   1.同步静态方法

      private static synchronized t1(){

      }

   2.同步方法

      private synchronized t2(){

      }

   3.同步代码块

      synchronized(object){

      }

   4.线程同步

      synchronized(object){

            object.wait();//等待,保证当前线程已经获取了该对象的锁,否则调用wait会报异常;调用该方法,会强制升级到重量级锁,因为wait方法需要ObjectMonitor对象

            object.notify();//通知,保证当前线程已经获取了该对象的锁,否则调用wait会报异常,会强制升级到重量级锁,因为wait方法需要ObjectMonitor对象

      }

二.synchronized锁机制

     i. 相关数据结构

      synchronized锁机制需要java对象参与,也需要对象对应的ObjectMonitor参与。

      当锁升级到重锁时,就要生成一个ObjectMonitor对象来实现重锁;偏向锁和轻量级锁时,不需要生成对象对应的ObjectMonitor对象。

      先看看java对象结构

      java对象由如下几部分组成:

           1. 对象头:Mark word和klasspointer两部分组成;如果是数组,还包括数组长度

           2. 实例属性

           3. 对齐填充

       如下图所示:

       

               注意:图中 Class Pointer 应该是“klass pointer”,应该是类的元数据,不是其在堆中的Class对象

 

              其中Mark Word 对象示意图:

     

 

 

       ii.获取锁的过程如下:

       1.如果当前对象没有生成过hashCode,那可以申请偏向锁(注意:如果对象计算过hashCode,会缓存到MarkWord中,后续申请锁的话,无法申请偏向锁;因为偏向锁机制没有保存原来的MarkWord值,如果还让申请偏向锁会导致hashCode丢失,如果重新计算hashCode 可能会导致和上次计算的hashCode不一样了(有的对象的hashCode计算函数就是生成一个随机数),这违反了java对象生成之后hashCode不会改变这个原则),否则只能申请轻量级锁。

          hashCode和偏向锁不能共存:

               当一个对象已经计算过identity hash code,它就无法进入偏向锁状态;

               当一个对象当前正处于偏向锁状态,并且需要计算其identity hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为轻量级锁或者重量锁;

       2.在栈中构建lock recode,把lock recode中的 object属性设置为当前对象地址,设置当前线程id到markword中,同时设置偏向锁标志为1,(注意,偏向锁没有改变MarkWord中的“分代年龄”字段)

       3.如果申请锁时,已经被其他线程申请了偏向锁了,会导致锁升级

          3.1 暂停所有线程,如果持锁线程还在使用锁(说明线程同时竞争锁),直接升级到重量级锁(跳到步骤4);如果持锁线程不用锁了,跳到3.2

          3.2 撤销偏向锁,把MarkWord设置为无锁状态

          3.3 为设置轻量级锁,先保存当前MarkWord到 lock recode中

          3.4 设置当前lock recode地址到MarkWord中,设置锁标志为0,完成轻量级锁设置

          3.5 唤醒所有线程,当前线程通过cas获取轻量级锁

          3.6 如果当前线程获取失败,升级到重量级锁

       4.锁升级到重量级锁

          4.1 暂停所有线程

          4.2 新建一个ObjectMonitor对象,把该对象地址保存到MarkWord中,修改锁标志为2

          4.3 当前线程通过cas自旋获取锁,如果自旋一段时间还未获得锁,进入睡眠

          4.4 当前线程进入ObjectMonitor对象的等待队列,并进入睡眠状态,等待唤醒

          4.5 持锁线程进入ObjectMonitor对象的执行队列,执行完临界区代码之后,会发现自己持有的锁,已经变成重量级锁了,会在退出临界区之前,唤醒其他等锁的线程

        

       iii.原理图

     

 

 

 

 该图只是示意图,好像逻辑不大对

       

三.底层原理

     synchronized底层调用c++代码,实际使用操作系统的mutex(互斥量)来实现锁机制和操作系统的condition(条件变量)来实现同步机制(就是等待wait,通知nodify) 

 

四.锁可以降级 

网上说锁不可以降级,是不对的;可以降级

降级的目的和过程:

因为BasicLocking的实现优先于重量级锁的使用,JVM会尝试在SWT的停顿中对处于“空闲(idle)”状态的重量级锁进行降级(deflate)。这个降级过程是如何实现的呢?我们知道在STW时,所有的Java线程都会暂停在“安全点(SafePoint)”,此时VMThread通过对所有Monitor的遍历,或者通过对所有依赖于MonitorInUseLists值的当前正在“使用”中的Monitor子序列进行遍历,从而得到哪些未被使用的“Monitor”作为降级对象。

可以降级的Monitor对象:

重量级锁的降级发生于STW阶段,降级对象就是那些仅仅能被VMThread访问而没有其他JavaThread访问的Monitor对象,删除这些Monitor对象,把对应对象的对象头的Mark恢复到重量级锁之前的状态。

相关代码在:ObjectSynchronizer::deflate_idle_monitors() 

 

注:文中图片来自网络,如果有侵权,请联系我!!!      

 

 

      

posted @ 2022-02-16 16:29  高压锅里的大萝卜  阅读(126)  评论(0编辑  收藏  举报