Synchronized锁了个什么玩意

席晓明

共 11852字,需浏览 24分钟

 ·

2023-06-30 11:25

微信公众号:saikou4java
问题或建议,请公众号留言;

Synchronized锁了个什么玩意

先大胆假设,如果让我们自己实现一个锁一样的功能,应该怎么实现呢。某个资源,我在使用,别人就不能用,必须等我使用完毕才能用,假设使用全局变量来控制。

使用伪代码完成上面的逻辑

// 定义一个静态变量
static boolean isLock = false;
// 加锁的方法
public static void lock() {
    while (true) {
        // 如果锁是打开的,获取并关闭锁,否则循环等待
        if (!isLock) {
            isLock = true;
            break;
        }
    }
}
// 解锁的方法
private static void release() {
    isLock = false;
}

伪代码是不可用的,可以猜想一下synchronized是不是也是通过某个标志来控制的呢,我们写一个方法,通过生成的.class文件来查看一下

public static void main(String[] args) {
    int a = 0;
    synchronized (App3.class){
        a++;
    }
}

对应的.class文件反编译为

public static void main(String[] args) {
    int a = 0;
    Class var2 = App3.class;
    synchronized(App3.class) {
        int var5 = a + 1;
    }
}

对应的jvm指令为

public static void main(java.lang.String[]);
   Code:
       0: iconst_0     // a值入栈
       1: istore_1     // a值出栈,保存到局部变量中
       2: ldc          // 常量池中的引用var2入栈
       4: dup          // 复制栈顶的var2,再次入栈
       5: astore_2     // var2出栈,保存到局部变量中
       6: monitorenter    // var2出栈,获取对象监视器
       7: iinc  11       // 局部变量a加1
      10: aload_2       // 从局部变量中获取var2,再次入栈
      11: monitorexit    // var2出栈,释放并退出对象监视器
      12: goto  20         // 无条件跳转到指定位置
      ...                //  异常处理
      20return          // void函数返回

从上面的指令可以看出,synchronized关键字转化为了指令monitorenter和monitorexit,那么我们只需要知道这两条指令是什么意思就行了。

monitorenter和monitorexit是什么

java虚拟机规范中对monitorenter描述为:Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref.
谷歌翻译一下就是:每个object都与一个monitor关联。monitor只有在拥有所有者的情况下才会被锁定。执行monitorenter的线程会尝试获得与objectref关联的监视器的所有权。

那么我们有两个问题

  1. monitors是个什么东西

  2. monitorobject是怎么关联的

首先看monitors是什么东西,在软件的世界里,只有代码,既然jvm也是一个软件,那我们找到monitors的代码就知道了。这个代码在openjdk11\src\hotspot\share\runtime包下,有个类叫objectMonitor.cpp。虽然是个2446行的c++代码,完全看不懂,但是只要找到关键方法就可以,此处可以求助陈师兄--!。

void ObjectMonitor::enter(TRAPS) {
      // 看到Thread倍感情切,也就能看懂这个单词了。
      // 定义当前线程self
      Thread * const Self = THREAD;
      // 和java中的cas类似,将对象的owner设置为当前线程self
      // 这一步就是用来获取锁的
      void * cur = Atomic::cmpxchg(Self, &_owner, (void*)NULL);
      // 获取锁失败
      if (cur == NULL) {
        assert(_recursions == 0"invariant");
        assert(_owner == Self, "invariant");
        return;
      }
      // 同一个线程可以重复获取锁
      // _recursions记录获取锁的次数
      if (cur == Self) {
        _recursions++;
        return;
      }
      // 第一次获取锁成功进行初始化,设置了_recursions
      // 断言只有在_recursions==0的时候才可以获取锁
      if (Self->is_lock_owned ((address)cur)) {
        assert(_recursions == 0"internal state error");
        _recursions = 1;
        _owner = Self;
        return;
      }
      assert(Self->_Stalled == 0"invariant");
      Self->_Stalled = intptr_t(this);
      if (Knob_SpinEarly && TrySpin (Self) > 0) {
        assert(_owner == Self, "invariant");
        assert(_recursions == 0"invariant");
        assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
        Self->_Stalled = 0;
        return;
      }
      assert(_owner != Self, "invariant");
      assert(_succ != Self, "invariant");
      assert(Self->is_Java_thread(), "invariant");
      JavaThread * jt = (JavaThread *) Self;
      assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
      assert(jt->thread_state() != _thread_blocked, "invariant");
      assert(this->object() != NULL"invariant");
      assert(_count >= 0"invariant");
      Atomic::inc(&_count);
      JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);)
      EventJavaMonitorEnter event;
      if (event.should_commit()) {
        event.set_monitorClass(((oop)this->object())->klass());
        event.set_address((uintptr_t)(this->object_addr()));
      }
      {
        JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
        Self->set_current_pending_monitor(this);
        DTRACE_MONITOR_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);
        for (;;) {
          jt->set_suspend_equivalent();
          EnterI(THREAD);
          if (!ExitSuspendEquivalent(jt)) break;
          _recursions = 0;
          _succ = NULL;
          exit(false, Self);
          jt->java_suspend_self();
        }
        Self->set_current_pending_monitor(NULL);
      }
      Atomic::dec(&_count);
      assert(_count >= 0"invariant");
      Self->_Stalled = 0;
      assert(_recursions == 0"invariant");
      assert(_owner == Self, "invariant");
      assert(_succ != Self, "invariant");
      assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
      DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
      if (JvmtiExport::should_post_monitor_contended_entered()) {
        JvmtiExport::post_monitor_contended_entered(jt, this);
      }
      if (event.should_commit()) {
        event.set_previousOwner((uintptr_t)_previous_owner_tid);
        event.commit();
      }
      OM_PERFDATA_OP(ContendedLockAttempts, inc());
    }

虽然上面的方法完全看不懂,但是我们已经好像得到了想要的东西,monitor通过cas的方式把owner设置为当前线程,如果owner已经被其他线程设置过了,直接设置失败,即获取锁失败,好像跟我们伪代码里写的那个isLock作用一样,原来jvm你也搞了个变量来控制。在第一次获取锁成功的时候,设置了recursions = 1,并且断言了只有recursions = 0的时候才可以获取成功,为了保证锁可以被一个线程获取多次,使用了recursions ++,可以猜测,释放锁肯定使用了recursions --,直到减为0,也就是说一个线程获取的锁必须全部释放了,其他线程才有可能调用添加锁的方法。

再来看看释放锁的方法

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
  Thread * const Self = THREAD;
  // 先判断当前线程是否等于owner
  // 有两种情况
  if (THREAD != _owner) {
    // 轻量级锁情况的释放
    if (THREAD->is_lock_owned((address) _owner)) {
      assert(_recursions == 0"invariant");
      _owner = THREAD;
      _recursions = 0;
    } else {
      // 真的不是一个线程,直接退出
      // 会抛出IllegalMonitorStateException异常
      // 某个线程的锁不能由其他线程释放
      TEVENT(Exit - Throw IMSX);
      assert(false"Non-balanced monitor enter/exit! Likely JNI locking");
      return;
    }
  }

  if (_recursions != 0) {
    // 加多少次锁就释放多少次
    _recursions--;
    TEVENT(Inflated exit - recursive);
    return;
  }
  if ((SyncFlags & 4) == 0) {
    _Responsible = NULL;
  }

#if INCLUDE_JFR
  if (not_suspended && EventJavaMonitorEnter::is_enabled()) {
    _previous_owner_tid = JFR_THREAD_ID(Self);
  }
#endif

  for (;;) {
    assert(THREAD == _owner, "invariant");
    if (Knob_ExitPolicy == 0) {
      // 这一步就是用来释放锁的,看方法名字
      OrderAccess::release_store(&_owner, (void*)NULL);
      OrderAccess::storeload(); 
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        TEVENT(Inflated exit - simple egress);
        return;
      }
      TEVENT(Inflated exit - complex egress);
      if (!Atomic::replace_if_null(THREAD, &_owner)) {
        return;
      }
      TEVENT(Exit - Reacquired);
    } else {
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        OrderAccess::release_store(&_owner, (void*)NULL);
        OrderAccess::storeload();
        if (_cxq == NULL || _succ != NULL) {
          TEVENT(Inflated exit - simple egress);
          return;
        }
        if (!Atomic::replace_if_null(THREAD, &_owner)) {
          TEVENT(Inflated exit - reacquired succeeded);
          return;
        }
        TEVENT(Inflated exit - reacquired failed);
      } else {
        TEVENT(Inflated exit - complex egress);
      }
    }
    guarantee(_owner == THREAD, "invariant");
    ObjectWaiter * w = NULL;
    int QMode = Knob_QMode;
    if (QMode == 2 && _cxq != NULL) {
      w = _cxq;
      assert(w != NULL"invariant");
      assert(w->TState == ObjectWaiter::TS_CXQ, "Invariant");
      ExitEpilog(Self, w);
      return;
    }
    if (QMode == 3 && _cxq != NULL) {
      w = _cxq;
      for (;;) {
        assert(w != NULL"Invariant");
        ObjectWaiter * u = Atomic::cmpxchg((ObjectWaiter*)NULL, &_cxq, w);
        if (u == w) break;
        w = u;
      }
      assert(w != NULL"invariant");

      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }
      ObjectWaiter * Tail;
      for (Tail = _EntryList; Tail != NULL && Tail->_next != NULL;
           Tail = Tail->_next)
      if (Tail == NULL) {
        _EntryList = w;
      } else {
        Tail->_next = w;
        w->_prev = Tail;
      }
    }
    if (QMode == 4 && _cxq != NULL) {
      w = _cxq;
      for (;;) {
        assert(w != NULL"Invariant");
        ObjectWaiter * u = Atomic::cmpxchg((ObjectWaiter*)NULL, &_cxq, w);
        if (u == w) break;
        w = u;
      }
      assert(w != NULL"invariant");

      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }
      if (_EntryList != NULL) {
        q->_next = _EntryList;
        _EntryList->_prev = q;
      }
      _EntryList = w;
    }
    w = _EntryList;
    if (w != NULL) {
      assert(w->TState == ObjectWaiter::TS_ENTER, "invariant");
      ExitEpilog(Self, w);
      return;
    }
    w = _cxq;
    if (w == NULLcontinue;
    for (;;) {
      assert(w != NULL"Invariant");
      ObjectWaiter * u = Atomic::cmpxchg((ObjectWaiter*)NULL, &_cxq, w);
      if (u == w) break;
      w = u;
    }
    TEVENT(Inflated exit - drain cxq into EntryList);
    assert(w != NULL"invariant");
    assert(_EntryList == NULL"invariant");
    if (QMode == 1) {
      ObjectWaiter * s = NULL;
      ObjectWaiter * t = w;
      ObjectWaiter * u = NULL;
      while (t != NULL) {
        guarantee(t->TState == ObjectWaiter::TS_CXQ, "invariant");
        t->TState = ObjectWaiter::TS_ENTER;
        u = t->_next;
        t->_prev = u;
        t->_next = s;
        s = t;
        t = u;
      }
      _EntryList  = s;
      assert(s != NULL"invariant");
    } else {
      _EntryList = w;
      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }
    }
    if (_succ != NULLcontinue;
    w = _EntryList;
    if (w != NULL) {
      guarantee(w->TState == ObjectWaiter::TS_ENTER, "invariant");
      ExitEpilog(Self, w);
      return;
    }
  }
}

基本就认识几个单词,再次@陈师兄--!,但是可以看到跟我们猜想的过程一样,释放锁的时候先recursions--,再把owner重置。所以synchronized关键字锁的是个什么东西呢,锁的是object,锁的是object关联的monitor,锁的是object关联的monitor里面的owner,都说得过去。

monitorobject是怎么关联的

java对象之间的关系,有组合有引用有继承有实现,但是我们从来没有见过一个monitor这样的类,猜测一下,如果我们想让一个对象关联另一个对象,传入另一个对象的引用就行了,引用就是这个对象在内存中的地址值,所以我们的对象可能也在某个地方保存了monitor的地址值,要搞清楚这个问题,必须知道对象里的结构是什么。

根据《深入理解java虚拟机》,在HotSpot虚拟机中,对象在内存中存储的布局可以分为3个区域,对象头(Header),实例数据(Instance Data),对齐填充(Padding)

Header又分为两部分,第一部分用于存储对象自身的运行时数据,如哈希码,GC分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分官方称为Mark Word。第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

关于对象头中的信息可以查看 openJdk中 markOop.hpp的注释。

1843fb3a1c3ffec726a3a4602de51049.webp
我们就把对象头打印出来看看,根据不同的锁状态,对象头中的数据代表的含义会发生变化,64位intel cpu使用小端存储,最高2bit10就是上面注释中的lock,代表对象持有重量级锁,其余62位代表monitor的指针。


OFFSET  SIZE   TYPE DESCRIPTION              VALUE
 0     4        (object header)             da f5 28 1d (11011010 11110101 00101000 00011101) (489223642)
 4     4        (object header)             00 00 00 00 (00000000 00000000 00000000 00000000) (0)
 8     4        (object header)             05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)
12     4       (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

后记

以上内存不保证任何正确性,纯属猜测,没有经过证明。



下面的是我的公众号二维码图片,欢迎关注。

浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报