您现在的位置是:首页 > 编程 > 

JVM原理与实现——Synchronized关键字

2025-07-26 14:26:39
JVM原理与实现——Synchronized关键字 在多线程的Java程序中,Synchronized关键字是经常出现的。这篇文章里,我们就来研究一下它的实现原理。 比如以下的示例程序: 代码语言:javascript代码运行次数:0运行复制aspectj 代码解读复制代码public class SynchronizedTest { int syncFunc() {

JVM原理与实现——Synchronized关键字

在多线程的Java程序中,Synchronized关键字是经常出现的。这篇文章里,我们就来研究一下它的实现原理。 比如以下的示例程序:

代码语言:javascript代码运行次数:0运行复制
aspectj 代码解读复制代码public class SynchronizedTest {
    int syncFunc() {
        synchronized(this) {
            int a = 0;
            return a;
        }
    }
}

对应的字节码如下:

代码语言:javascript代码运行次数:0运行复制
yaml 代码解读复制代码Compiled from "SynchronizedTest.java"
public class SynchronizedTest {
  public SynchronizedTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  int syncFunc();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       : monitorenter
       4: ict_0
       5: istore_2
       6: iload_2
       7: aload_1
       8: monitorexit
       9: ireturn
      10: astore_
      11: aload_1
      12: monitorexit
      1: aload_
      14: athrow
    Exception table:
       from    to  target type
           4     9    10   any
          10    1    10   any
}

Java编译器为synchronized关键字生成了monitorenter和monitorexit字节码。这两个关键字把临界区包裹起来,实现了函数的线程安全。所以我们需要研究一下monitorenter和monitorexit字节码的工作原理。换句话说,就是要到JVM是如何解释执行这两个字节码的。

谈到Java解释器就说来话长了,因为JVM中有多个Interpreter的实现。虽然它们的效率有比较大的差异,但可用性都是有保证的,毕竟Java都二十多年了。这里为了简单起见,我就拿比较初级的BytecodeInterpreter分析了。这个字节码解释器有一个重要的run()方法,它就是实现字节码解析和执行的函数。虽然功能听起来挺高大上的,但实际上就是一个很大的switch语句。不信你来看代码!

代码语言:javascript代码运行次数:0运行复制
reasonml 代码解读复制代码  while (1)
  {
      opcode = *pc;
      switch (opcode)
      {
      CASE(_nop):
          UPDATE_PC_AD_COTIUE(1);
          /* Push miscellaneous ctants onto the stack. */
      CASE(_act_null):
          SET_STACK_OBJECT(ULL, 0);
          UPDATE_PC_AD_TOS_AD_COTIUE(1, 1);
          /* Push a 1-byte signed integer value onto the stack. */
      CASE(_monitorenter): {
        oop lockee = STACK_OBJECT(-1);
        // derefing's lockee ought to provoke implicit null check
        CHECK_ULL(lockee);
        // find a free monitor or one already allocated for this object
        // if we find a matching object then we need a new monitor
        // since this is recursive enter
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        BasicObjectLock* entry = ULL;
        while (most_recent != limit ) {
          if (most_recent->obj() == ULL) entry = most_recent;
          else if (most_recent->obj() == lockee) break;
          most_recent++;
        }
        if (entry != ULL) {
          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) {
            // Is it simple recursive case?
            if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
              entry->lock()->set_displaced_header(ULL);
            } else {
              CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
            }
          }
          UPDATE_PC_AD_TOS_AD_COTIUE(1, -1);
        } else {
          istate->set_msg(more_monitors);
          UPDATE_PC_AD_RETUR(0); // Re-execute
        }
      }
  }

这段代码被各种删减之后,你会发现它是如此的简单。while循环加switch,确定不是《程序设计基础》第二章的例题?虽然这段代码已经非常简单了,但是你依然可以清晰的看到我们的主角——_monitorenter,这个重要的字节码。它的兄弟字节码_moniterexit,我们先按下不表。等我们搞清楚_moniterenter,你会发现_moniterexit只是一个逆向操作而已。

1. lightweight locked(轻度锁)

然而这个moniterenter却很不简单呀!先看if (entry != ULL)之前的代码,这段代码的功能就是尝试分配一个可用的entry。关于这个entry的使用,就到了if语句之后的部分了。这段代码看似简单,但还是需要一些背景知识才可能理解的。首先需要介绍的是Mark Word,这是每一个Object对象都拥有的一个域。在2位的机器上,Mark Word的长度也是2位,它的具体含义如下图:

mark_word

图中的每一行代表一个独立状态,状态之间是互斥的。第一种状态是Object的正常状态,第二种状态是轻度锁的状态第三种是moniter重度锁状态,第五种是偏向锁状态。我们首先把重点放在前三种状态上,来研究Object的锁是如何工作的。为了方便理解,贴两图出来。下面这张图是进入if语句之前的状态:

mark_word_normal

这里的Lock record就是刚刚提到的entry变量,而Object对象头部的mark word也表示它还是个“单身汉”。接下来,当前线程会尝试进入轻度锁状态。这个过程如下图所示:

mark_word_cas

if语句内部的前三行代码实际上就是完成了Lock record的赋值,这其中包括根据Object的mark word设置header,以及把entry的obj(也就是owner)指向Object。然后就到了激动人心的时刻,该CAS出场了,能不能拿到轻量锁就在此一举了!

这个CompareAndSwap的作用就是——比较Object的mark word和Lock record的header是否相同。如果相同,那么就把entry的地址赋值给mark word;如果不同,说明Object已经名花有主了,然后尝试其他方式。这里有一个非常有趣的trick,值得和大家一说。为什么轻量锁状态的tag bits是00,而不是其他值呢?答案就是,在CAS的成功操作中,会把entry的地址赋值给mark word。一般来说,地址值的低两位会有多种组合,但是如果我们在构造entry是按4字节对齐,那么它地址的低两位就一定是00。而且4字节对齐会更容易实现,所以就把tag bits规定为这样了。

2. monitor重度锁

前面的代码中,如果CAS成功,那么就直接进入后续的字节码执行了,所以我们要研究CAS失败的情况。如果失败了,首先会检查是不是当前线程重入了,如果是,那么也相当于成功了。如果判断也不是当前线程重入,那就到了InterpreterRuntime::monitorenter(THREAD, entry), handle_exception)的表演时刻了。

实际上InterpreterRuntime::monitorenter()方法也会再尝试一次CAS,因为一段时间之后,锁可能已经被释放了。如果还是失败,就没办法了——进入monitor重度锁吧!这个操作是由ObjectMonitor::enter()完成的。

代码语言:javascript代码运行次数:0运行复制
rust 代码解读复制代码void ATTR ObjectMonitor::enter(TRAPS) {
   Thread * ct Self = THREAD ;
   void * cur ;
  Atomic::inc_ptr(&_count);

  EventJavaMonitorEnter event;

  { // Change java thread status to indicate blocked on monitor enter.
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

    DTRACE_MOITOR_PROBE(contended__enter, this, object(), jt);
    if (JvmtiExport::should_post_monitor_contended_enter()) {
      JvmtiExport::post_monitor_contended_enter(jt, this);
    }

    OSThreadContendState osts(Self->osthread());
    ThreadBlockInVM tbivm(jt);

    Self->set_current_pending_monitor(this);

    // TODO-FIXME: change the following for(;;) loop to straight-line code.
    for (;;) {
      jt->set_suspend_equivalent();

      EnterI (THREAD) ;

      if (!ExitSuspendEquivalent(jt)) break ;

      exit (false, Self) ;

      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(ULL);
  }

   Atomic::dec_ptr(&_count);
   assert (_count >= 0, "invariant") ;
   Self->_Stalled = 0 ;

  if (ObjectMonitor::_sync_ContendedLockAttempts != ULL) {
     ObjectMonitor::_sync_ContendedLockAttempts->inc() ;
  }
}

这个函数会在for循环中不断检查是否拿到了Moniter锁,如果拿到了就成功的执行完了这个monitorenter字节码了。这里就先不介绍monitorexit了。还有就是偏向锁也没有介绍,等后续有需要的话再写了。

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1222291.html

相关标签:无
上传时间: 2025-07-25 18:28:11
留言与评论(共有 16 条评论)
本站网友 胎儿体重
1分钟前 发表
entry)
本站网友 佣金什么意思
9分钟前 发表
以及把entry的obj(也就是owner)指向Object
本站网友 上海黄金交易所今日金价
25分钟前 发表
会把entry的地址赋值给mark word
本站网友 重庆渝北房屋出租
6分钟前 发表
它的兄弟字节码_moniterexit
本站网友 mect是什么意思
28分钟前 发表
第一种状态是Object的正常状态
本站网友 龙眼的主要功效
9分钟前 发表
毕竟Java都二十多年了
本站网友 嫦娥四号中继星
27分钟前 发表
本站网友 嫖妓
17分钟前 发表
SET_STACK_OBJECT(ULL
本站网友 附睾炎图片
17分钟前 发表
athrow Exception table
本站网友 思源电气招聘
16分钟前 发表
关于这个entry的使用
本站网友 妈富隆
5分钟前 发表
因为JVM中有多个Interpreter的实现
本站网友 阿尔瓦利德
13分钟前 发表
就是要到JVM是如何解释执行这两个字节码的
本站网友 batterymon怎么用
1分钟前 发表
在2位的机器上
本站网友 提价通知
25分钟前 发表
aload_1 8
本站网友 branches
2分钟前 发表
我就拿比较初级的BytecodeInterpreter分析了