Java多线程- synchronized 工作原理分析-下

1 synchronized指令实现

通过前面一篇文章我们可知道 synchronized有两种使用方式一种是在方法上,一中是在代码块使用。在方法上我们通过分析字节码发现是在方法标志上设置ACC_SYNCHRONIZED标志代码块上的实现是通过 monitorenter指令和monitorexit的指令来实现了一段代码缓冲区。

2 synchronized过程解析

2.1 ACC_SYNCHRONIZED过程解析

bytecodeInterpreter 是jvm 执行过程中的编译器,主要对字节码进行解释执行。在解释执行方法的过程发现如果发下方法有ACC_SYNCHRONIZED会先进行拿锁,拿到锁的之后才会执行。具体流程如下:

从上图可以清晰看出当是ACC_SYNCHRONIZED方法的时候会等到拿到锁的过程才开始执行。下面是源码信息:

case method_entry: {
      THREAD->set_do_not_unlock();
     //在这中间去掉了一些无关代码    
      //synchronized 整个synchronized方法实现的过程
      if (METHOD->is_synchronized()) {
          oop rcvr;
           //如果方法是static的 要获取到类操作对象
          if (METHOD->is_static()) {
            rcvr = METHOD->constants()->pool_holder()->java_mirror();
          } else {
            //获取到当前实例的操作对象
            rcvr = LOCALS_OBJECT(0);
            VERIFY_OOP(rcvr);
          }
          // 线程栈的存储监控对象的数组
          BasicObjectLock* mon = &istate->monitor_base()[-1];
          //获取到对应操作对象
          oop monobj = mon->obj();
          //判断是否启动偏向锁
          bool success = UseBiasedLocking;
          //如果启用偏向锁 现在的jdk里面默认是不启用的这段代码可以直接跳过
          if (UseBiasedLocking) {
            markOop mark = rcvr->mark();
            //主要用来判断是否已经拿了偏向锁,如果拿了是否是当前线程
            if (mark->has_bias_pattern()) {
              //如果不是当前线程的就直接失败
            } else {
              cas_label:
              success = false;
            }
          }
          //如果没有启动偏向锁 或者偏向锁失败
          if (!success) {
           //设置锁对象
            markOop displaced = rcvr->mark()->set_unlocked();
            mon->lock()->set_displaced_header(displaced);
            //尝试进行获取锁对象 ,如果成功就不执行里面内容
            if (Atomic::cmpxchg_ptr(mon, rcvr->mark_addr(), displaced) != displaced) {
              // 如果已经拿到锁了是重入的情况
              if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
                mon->lock()->set_displaced_header(NULL);
              } else {
                //调用标准的monitorenter 进入锁竞争机制
                CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
              }
            }
          }
      }
      THREAD->clr_do_not_unlock();
     //执行代码
      goto run;
    }    

 通过上图中代码,我们最终可以清晰看到是通过InterpreterRuntime::monitorenter(THREAD, mon)来实现获取锁的调用的。

2.2 monitorenter 指令解析过程

bytecodeInterpreter当判断代码是_monitorenter指令进行编译,加入整个获取锁的逻辑。

 

核心的关键点:

1 从栈的底部挨个寻找空闲的锁的对象指针,获取到空闲的对象绑定锁。这就是简单的对象池技术和数据库连接池类似,重复利用空闲的空间。

2 尝试加入偏向锁,如果失败了就启动InterpreterRuntime::monitorenter 来通过锁晋升的机制拿锁。

JVM的bytecodeInterpreter代码如下:

CASE(_monitorenter): {
  //获取synchronized 同步的对象
  oop lockee = STACK_OBJECT(-1);
  //判断对象是否为空 为空就无法加锁了
  CHECK_NULL(lockee);
 //获取到锁结束的位置
  BasicObjectLock* limit = istate->monitor_base();
  //获取到栈开始位置
  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  BasicObjectLock* entry = NULL;
  //遍历已经获得的锁 判断是否有当前_monitorenter 要拿到的锁
  while (most_recent != limit ) {
    if (most_recent->obj() == NULL){
         entry = most_recent;
     }
    else if (most_recent->obj() == lockee) {
          break;
     }
    most_recent++;
  }
  //如果前面没有拿到过锁
  if (entry != NULL) {
    entry->set_obj(lockee);
    markOop displaced = lockee->mark()->set_unlocked();
    entry->lock()->set_displaced_header(displaced);
    //尝试加偏向锁
    if (Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
      if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
        entry->lock()->set_displaced_header(NULL);
      } else {
       //加锁失败获取重量级锁
        CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
      }
    }
    UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
  } else {
    istate->set_msg(more_monitors);
    UPDATE_PC_AND_RETURN(0); // Re-execute
  }
}

2.3 monitorexit 指令解析过程

bytecodeInterpreter判断代码中有monitorexit指令处理流程就是比较简单了。总过只完成两件事,一个是释放本地线程栈中找到要释放的对象,二调用InterpreterRuntime::monitorexit将锁对象释放掉就可以了。整个核心代码就如下:

CASE(_monitorexit): {
  oop lockee = STACK_OBJECT(-1);
  CHECK_NULL(lockee);
  // derefing's lockee ought to provoke implicit null check
  // find our monitor slot
  BasicObjectLock* limit = istate->monitor_base();
  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  //从线程栈的锁中找到当前要释放的锁对象
  while (most_recent != limit ) {
    if ((most_recent)->obj() == lockee) {
      BasicLock* lock = most_recent->lock();
      markOop header = lock->displaced_header();
      most_recent->set_obj(NULL);
      // If it isn't recursive we either must swap old header or call the runtime
      if (header != NULL) {
        if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
          // restore object for the slow case
          most_recent->set_obj(lockee);
          //将锁对线程锁象释放掉
          CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
        }
      }
      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
    }
    most_recent++;
  }
  // Need to throw illegal monitor state exception
  CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
  ShouldNotReachHere();
}

3 锁获取实现逻辑

通过前面的内容我们知道不论是ACC_SYNCHRONIZED方法标志还是_monitorenter指令最终都是nterpreterRuntime类monitorenter方法来获取锁的。

3.1 monitorenter方法

InterpreterRuntime的monitorenter方法逻辑也非常简单,就是判断是否启动偏向锁,如果启动偏向锁就调用ObjectSynchronizer类fast_enter方法来获取偏向锁,如果不启动就调用slow_enter获取轻量级锁。

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  Handle h_obj(thread, elem->obj());
  //判断是否开启偏向锁 ,如果开启fast_enter获取偏向锁
  if (UseBiasedLocking) {
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {
   //如果开启偏向锁调用slow_enter获取偏轻量级锁
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

如果fast_enter调用失败后就也会调用slow_enter来获取轻量级锁,如果slow_enter失败化就会调用inflate锁膨胀的机制来获取重量级锁的。整个锁的获取流程是fast_enter(偏向锁) ->slow_enter(轻量级锁)->inflate(重量级锁)来逐步升级获取锁机制的。

3.2 fast_enter方法

fast_enter 从字面上看就是快速获取到锁的方法,核心的实现是调BiasedLocking类revoke_and_rebias方法加偏向锁。本质是就是修改锁对象的mark word的值,把线程ID写入到锁对象的mark word值中。

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 if (UseBiasedLocking) {
     //不在安全点
    if (!SafepointSynchronize::is_at_safepoint()) {
       //删除以前的偏向锁标志 并重新加上偏向锁标志
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      //如果表示成功了直接返回
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return;
      }
    } else {
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
 }
//升级到轻量级锁
 slow_enter (obj, lock, THREAD) ;
}

如果获取失败就会调用slow_enter 来获取轻量级锁。

3.3 slow_enter方法

slow_enter就是来实现获取轻量级锁,本质就是lock的内存地址赋值给锁对象。设置成功了之后就自动就自动返回了。核心的代码是:

mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)

通过上面代清晰看到通过一次cas的操作来进行了地址赋值。

整个slow_enter整体代码如下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  //第一步 获取锁对象的“mark word"
  markOop mark = obj->mark();
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

  //第二步,判断当前锁是否是无锁状态 后两位标志位为01
  if (mark->is_neutral()) {
    // 锁中的Mark值和对象中的mark 值相同
    lock->set_displaced_header(mark);
    //通过CAS尝试将锁对象Mark Word更新为指向lock Record对象的指针,
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
  }
   //第四步,如果当前mark处于加锁状态,且线程帧栈中的owner指向当前锁,则执行同步代码,
  else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    //清除刚才设置的值
    lock->set_displaced_header(NULL);
    //获取成功了直接返回
    return;
  }
  //设置成初始化状态
  lock->set_displaced_header(markOopDesc::unused_mark());
  //进入锁膨胀的机制 先构造锁对象 然后加入锁对象等待队列
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

设计的巧妙点

前面在读 Mark Word的时候一直有个疑问为什么轻量级锁的前两位是00开头的?

通过上面的代码我们可以清晰的知道 lock地址最大范围是不能大不可能到达62位的,所以高位一定是00开头的。这样我们就巧妙通过了赋值直一次接修改了Mark Word的值。

3.4 inflate方法

inflate方法本质上就是获取到对象监视器ObjectMonitor然后当前线程加入到等待期里去等待。在多线程线程同时获取ObjectMonitor监视器的时候可能遇到如下几种情况:

1 其他线程已经获取到ObjectMonitor。

2 其他线程正在同时掉 inflate方法来获取ObjectMonitor过程中。

3 其他线程加了轻量级锁。

4 其他线程已经获取过锁并且释放掉了。

其实应对的策略也是非常简单靠谱的:

1 其他线程已经获取到ObjectMonitor,就直接返回其他线构建好的ObjectMonitor。

2 其他线程同时掉 inflate方法来获取ObjectMonitor过程中,情况不明先观望,后发制人。

3 其他线程加了轻量级锁,帮他深一级构建重量级锁ObjectMonitor。

4 其他线程已经获取过锁并且释放掉了,就构建一个新的ObjectMonitor。

核心的代码逻辑如下:

1.如果当前锁已经为重量级锁了,直接返回锁的monitor对象。

2.如果正在膨胀的过程中,在完成膨胀过程中,其他线程必须等待。

3.如果当前为轻量级锁,构造新的ObjectMonitor,使其膨胀为重量级锁。

4如果象是空闲的时候,重新构建一个ObjectMonitor,并将当前线程设置为owner。

代码细节如下:

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {

  //开始自旋转操作
  for (;;) {
      const markOop mark = object->mark() ;
      //1.如果当前锁已经为重量级锁了,直接返回
      if (mark->has_monitor()) {
          ObjectMonitor * inf = mark->monitor() ;
          return inf ;
      }
      //2.如果正在膨胀的过程中,在完成膨胀过程中,其他线程必须等待
      if (mark == markOopDesc::INFLATING()) {
         ReadStableMark(object) ;
         continue ;
      }
      //3.如果当前为轻量级锁,迫使其膨胀为重量级锁
      if (mark->has_locker()) {
          //构造一个monitor对象
          ObjectMonitor * m = omAlloc (Self) ;
          m->Recycle();
          m->_Responsible  = NULL ;
          m->OwnerIsThread = 0 ;
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class
          //先加个膨胀标志,紧紧是个标志 因为从轻量级到重量级需要一个复杂的过程
          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
          if (cmp != mark) {
             omRelease (Self, m, true) ;
             continue ;       // Interference -- just retry
          }
          //获取到当前的lock 标志
          markOop dmw = mark->displaced_mark_helper() ;
          //将他放入到moitor 头节点中来
          m->set_header(dmw) ;

         //设置锁的owner
          m->set_owner(mark->locker());
          //设置锁的对象
          m->set_object(object);
          //讲新的monitor对象就设置到对象头中
          object->release_set_mark(markOopDesc::encode(m));

          if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
          TEVENT(Inflate: overwrite stacklock) ;
          if (TraceMonitorInflation) {
            if (object->is_instance()) {
              ResourceMark rm;
              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                (void *) object, (intptr_t) object->mark(),
                object->klass()->external_name());
            }
          }
          //直接返回新的锁对象
          return m ;
      }

      //对象是空闲的时候 这就是踏空事件  代码跑到这时候 另一个线程刚刚释放了锁
      ObjectMonitor * m = omAlloc (Self) ;
      // prepare m for installation - set monitor to initial state
      m->Recycle();
      m->set_header(mark);
      m->set_owner(NULL);
      m->set_object(object);
      m->OwnerIsThread = 1 ;
      m->_recursions   = 0 ;
      m->_Responsible  = NULL ;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class
      //讲monitor 防止到对象的mark word中
      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
          m->set_object (NULL) ;
          m->set_owner  (NULL) ;
          m->OwnerIsThread = 0 ;
          m->Recycle() ;
          omRelease (Self, m, true) ;
          m = NULL ;
          continue ;
      }
     
      return m ;
  }
}

4 锁释放实现逻辑

同时ACC_SYNCHRONIZED 方法标志与monitorexit指令都是通过nterpreterRuntime类的monitorexit方法来释放锁的实现。

4.1 monitorexit方法

从下图代码中可以清晰的看到interpreterRuntime的monitorexit方法主要是通过ObjectSynchronizer类的slow_exit来释放获取锁对象的。

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  Handle h_obj(thread, elem->obj());
  if (elem == NULL || h_obj()->is_unlocked()) {
    THROW(vmSymbols::java_lang_IllegalMonitorStateException());
  }
  //释放对象
  ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);

  elem->set_obj(NULL);
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

4.2 slow_exit方法

slow_exit本是个封装方法,里面实现的逻辑是通过fast_exit来实现的。

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
  fast_exit (object, lock, THREAD) ;
}

4.2 fast_exit方法

通过前面加锁的过程我们可以清晰知道,偏向锁与轻量级锁只会修改锁对象的mark word的值,而重量级锁我们会整个构造一个锁监视对象ObjectMonitor,将线程加入到等待队列中。其实退出操作也刚好是逆向操作。如果是偏向锁就与轻量级锁就去掉mark word的值,如果重量级锁拿到ObjectMonitor监视器,然后将其从队列中除去。详细代码如下:

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  // 获取到锁对象的mark word
  markOop dhw = lock->displaced_header();
  markOop mark ;
  //如果线程堆栈中的Displaced Mark Word为null
  if (dhw == NULL) {
     //获取到对象的mark word
     mark = object->mark() ;
     //从monitor 中删除
     if (mark->has_monitor()) {
        ObjectMonitor * m = mark->monitor() ;
     }
     return ;
  }
  //获取到具体的mark值
  mark = object->mark() ;
  //锁中的mark和对象中mak相同 直接删除
  if (mark == (markOop) lock) {
     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
        TEVENT (fast_exit: release stacklock) ;
        return;
     }
  }
  //获取到锁监视器对象
  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值