所有对象都自动含有单一的锁与一个对象可以被多次加锁的矛盾?

网上有人说 所有对象都自动含有单一的锁。 又说JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候,…
关注者
87
被浏览
7,317
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

Java对象可以被当作锁(monitor)来使用,用于synchronized块或者synchronized修饰的方法。

Monitor并不是一个暴露给用户可见的Java对象。它是JVM的内部实现细节,对上层应用来说只能通过monitorenter / monitorexit字节码(用于实现Java语言的synchronized块)或由ACC_SYNCHRONIZED修饰的方法(用于实现Java语言中被synchronized关键字修饰的方法)所使用。

一个完整的monitor结构,语义上说大致会有这样的一些东西:

struct monitor_t {
  Object*   object;
  Thread*   owner;
  int       recursion_count;
  Thread**  wait_list;
  // ...
};

当一个Java对象被当作monitor使用时,它会有足够的内部数据结构(例如说在对象头里的隐藏字段)去找到它对应的monitor结构(例如上面示意用的monitor_t),而monitor自身通常会记录:

  • object:该monitor对应的Java对象是谁
  • owner:该monitor当前被哪个Java线程所持有
  • recursion_count:持有该monitor的线程递归锁了该monitor多少次
  • wait_list:还有哪些线程在等该monitor
  • … 其它一些每个JVM实现不同的具体信息,例如说具体平台上的mutex / semaphore等。

在这样的monitor_t结构上加锁(monitorenter)的时候一条快速路径就是递归加锁:(下面简易伪代码)

void monitor_enter(Object* obj, Thread* thread) {
  monitor_t* mon = obj->monitor();

  if (mon == nullptr) { // not locked yet
    monitor_t* new_mon = get_free_monitor(obj, thread);
    if (!install_monitor(obj, new_mon)) {
      // lost the race of installing the monitor onto the object header,
      // some other thread got to create and own the monitor first
      obj->monitor()->wait(thread);
    }
    mon = obj->monitor();
  }

  if (mon->owner() == thr) { // recursive locking
    mon->inc_recursion_count();
    return;
  }

  // non-recursive locking
  // ...
}

当然,现代高性能JVM都对monitor有各种优化来降低其空间和时间开销,所以并不总是会使用类似上面的monitor_t那样完整的结构。

在HotSpot VM里,由轻到重有:偏向锁(biased lock) -> 轻量锁(thin lock) -> 重量锁(inflated lock)这几个级别的锁。其中只有重量锁状态下会有一个“ObjectMonitor”结构跟上述示意用的monitor_t结构类似,而偏向锁和轻量锁都只需要在别的地方记录少量信息即可。

关于偏向锁与轻量锁,感兴趣的同学可以参考一下这个演示稿的第25-35页:

cs.princeton.edu/picass