关于java:JDK成长记15从0分析你不知道的synchronized底层原理上

40次阅读

共计 10467 个字符,预计需要花费 27 分钟才能阅读完成。

前几节你应该曾经理解和把握了 Thread、ThreadLocal、Volatile 这几个并发基础知识的底层原理。这一节,你能够跟我一起深刻理解下 synchronized 关键字的底层原理和其波及的基础知识。看完这篇成长记,你能够获取到如下几点:

synchronized 准备常识:

  • 了解什么是 CAS?
  • synchronized 会造成几种锁的类型
  • HotspotJVM 虚拟机 Java 对象内存中的布局构造是什么,markword 是锁的关键字段?
  • 操作系统中用户态和内核态的资源操作和过程是什么意思?

synchronized 外围流程及原理:

  • 从 3 个层面初步剖析 sychronized 的外围流程和原理

好了,让咱们一起开始吧!

HelloSychronized

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>HelloSychronized</span></h3></div>

咱们来写一个多线程 i ++ 的程序,体验一下,多线程如果是并发的批改一个数据,会有什么样的线程并发平安问题。

方才说过了,volatile,解决的对一个共享数据,有人写,有人读,多个线程并发读和写的可见性的问题,而多个线程对一个共享数据并发的写,可能会导致数据出错,产生原子性的问题。

volatile 为什么不能保障原子性? 从 JMM 内存模型就可以看进去,多个线程同时批改一个变量,都是在本人本地内存中批改,volatile 只是保障一个线程批改,另一个线程读的时候,发动批改的线程是强制刷新数据主存,过期其余线程的工作内存的缓存,没法做到多个线程在本地内存同时写的时候,限度只能有一个线程批改,因为线程本人批改本人内存的数据没有产生竞争关系。而且之后会给各自写入主内存,当然就保障不了只能有一个线程批改主内存的数据,做不到原子性了。

为了解决这个问题,能够应用 syncrhonized 给批改这个操作加一把锁,一旦说某个线程加了一把锁之后,就会保障,其余的线程没法去读取和批改这个变量的值了,同一时间,只有一个线程能够读这个数据以及批改这个数据,别的线程都会卡在尝试获取锁那儿。这样也就不会呈现并发同时批改,数据出错,原子性问题了。

synchronized 锁个别有两类,一种是对某个实例对象来加锁,另外一种是对这个类进行加锁。置信大家很相熟了,这里用一个 Hello synchronized 的小例子,举一个简略对象加锁的例子。

代码如下:

  public class HelloSynchronized {public static void main(String[] args) {Object o = new Object();
      synchronized (o){}}
  }

对类加锁和对实例对象的更多例子这里就不举例了,咱们更多的是钻研 synchronized 它的底层原理。根本的应用置信你肯定能够本人学习到。

在剖析 sychronized 原理期间,须要一直的补充一些基础知识。

学习 sychronized 先决条件(Prerequisites)

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 学习 sychronized 先决条件(Prerequisites)</span></h3></div>

  • sychronized 锁的概念

在 JDK 晚期 sychronized 应用的时候,间接创立的重量级锁,性能很不好。

在之后 JDK 新的版本中,sychronized 优化了锁,分为了 4 种,无锁态、偏差锁、自旋锁(轻量锁)、分量锁,会依据状况主动降级锁。

这四种锁别离示意什么意思呢?

无锁态 示意第一次对刚创立的对象或者类加锁时的状态。我发现只有一个线程在操作代码块的资源,压根不须要加锁。此时会处于无锁态。

偏差锁,相似于贴标签,示意这个资源临时属于某个线程,偏差它所有了。打个比方,就好比一个座位只能做一个人,你坐下后,在座位上贴上了你本人的标签。他人发现曾经有标签了,必定就不会在坐了。

轻量锁(自旋锁):轻量锁,底层是 CAS 自旋的操作,所以也叫自旋锁。这里简略遍及下自旋 CAS 的操作流程,之后将 Aotmic 类的时候会认真讲下。CAS 自旋流程如下:

最初咱们来聊下什么是 重量级锁?这又要牵扯另一个常识了。在 Linux 操作系统层面,因为须要限度不同的程序之间的拜访能力, 避免他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU 划分出两个权限等级用户态和内核态。用于示意过程运行时所处状态。

你能够简略了解,一个程序启动后会有对应的过程,它们操作的资源分为两种,属于用户态的资源或者内核态的资源。

用户态是不能间接操作内核态中资源的,只能告诉内核态来操作。这个在硬件级别也有对应的指令级别(比方 Intel ring0-ring3 级别的指令,ring0 级别个别对应的就是用户态过程能够操作的指令,ring3 对应的是内核态过程能够发动的指令)。

如下图所示:

这个和 synchronized 有什么关系呢?因为 synchronized 加重量级锁的操作,是对硬件资源的锁指令操作,所以必定是须要处于内核态的过程才能够操作,JVM 的过程只是处于用户态的过程,所以须要向操作系统申请,这个过程必定会很耗费资源的。

比方,synchronized 的实质是 JVM 用户空间的一个过程(处于用户态)向操作系统 (内核态) 发动一个 lock 的锁指令操作。

C++ 代码如下:

  //Adding a lock prefix to an instruction on MP machine
  \#define LOCK_IF_MP(mp) "cmp $0," #mp "; je 1f; local; 1 :"

如下图左边所示:

  • sychronized 锁状态的记录

理解了 sychronized 的锁的几种类型后,怎么标识是什么样的 synchronized 锁呢?这个就要聊到 Java 的对象在 JVM 的内存中的构造了。不同虚拟机构造略有差异,这里讲一下 HotSpot 虚拟机中的对象构造:

synchronized 锁状态的信息就记录在 markword 中。markword 在 64 位的操作系统上,8 字节,64 位大小的空间的区域。

不同的锁的标记在如下图所示:

这个表你不必背下来,你只有晓得,synchronized 的轻量锁和分量锁通过 2 位即能够辨别进去,偏差锁和无锁须要 3 位。

有了下面的基础知识后,就能够开始钻研 synchronized 的底层原理了。

字节码层面的 synchronized

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>synchronized</span></h3></div>

sychronized 在 Java 代码层面就如下面 Hello Synconized 那个最简略的例子所示,咱们来看下它的字节码层面是什么样子的?

下面 main 办法的字节码如下:

0 new #2 <java/lang/Object>
 3 dup
 4 invokespecial #1 <java/lang/Object.<init>>
 7 astore_1
 8 aload_1
 9 dup
 10 astore_2
 11 monitorenter
 12 aload_2
 13 monitorexit
 14 goto 22 (+8)
 17 astore_3
 18 aload_2
 19 monitorexit
 20 aload_3
 21 athrow
 22 return

new、dup、invokespecial、astore_1 这些指令是学习 volatile 的时候你应该很相熟了。我这里须要关注的是另外 2 个外围的 JVM 指令:monitorenter、monitorexit

这个示意 sychronized 加锁的同步代码块的进入和退出。为什么有两个 monitorexit 呢?一个是失常退出,一个抛出异样也会退出开释锁。

JVM 层面的 synchronized

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>JVM 层面的 synchronized</span></h3></div>

那么,当 JVM 的 HotSpot 实现中,当遇到这两个 JVM 指令,又是如何执行的呢?让咱们来看一下。

在 JVM HotSpot 的 C ++ 代码理论执行过程中,执行了一个 InterpreterRuntime:: monitorenter 办法,代码如下:

  IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))

  \#ifdef ASSERT
   thread->last_frame().interpreter_frame_verify_monitor(elem);
  \#endif

   if (PrintBiasedLockingStatistics) {Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
   }

   Handle h_obj(thread, elem->obj());

  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
       "must be NULL or an object");
   if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
   } else {ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
   }

   assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),

       "must be NULL or an object");

  \#ifdef ASSERT

   thread->last_frame().interpreter_frame_verify_monitor(elem);

  \#endif

  IRT_END

你能够看下下面的办法的脉络(不懂 C ++ 也没有关系,懂 if-else 就行)。它的外围有两个 if。

第一个 if 依据变量名字 PrintBiasedLockingStatistics 能够判断出应该是打印偏差锁的统计信息,显著不是最重要的。

第二个 if 同理,UseBiasedLocking 示意了是否应用了偏差锁,如果是调用了 ObjectSynchronizer::fast_enter 否则

ObjectSynchronizer::slow_enter。

很显著,第二个 if 中是 synchronized 加锁的外围代码。咱们还须要持续看下它们的脉络。

代码如下:synchronizer.cpp

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) ;

    }

能够看到 fast_enter 办法,外围脉络除了勾销偏差和从新偏差的逻辑(从变量明和正文能够看进去,这里临时不重要,先疏忽),最初外围脉络还是调用了 slow_enter 办法。让咱们来看下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {markOop mark = obj->mark();
   assert(!mark->has_bias_pattern(), "should not see bias pattern here"); 

   if (mark->is_neutral()) {
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {TEVENT (slow_enter: release stacklock) ;
     return ;
    }
    // Fall through to inflate() ...} else

   if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {assert(lock != mark->locker(), "must not re-lock the same lock");
    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL);
    return;
   }


  \#if 0
   // The following optimization isn't particularly useful.
  if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {lock->set_displaced_header (NULL) ;
    return ;
   }
  \#endif   

   // The object header will never be displaced to this lock,
   // so it does not matter what the value is, except that it
   // must be non-zero to avoid looking like a re-entrant lock,
   // and must not look locked either.
   lock->set_displaced_header(markOopDesc::unused_mark());
   ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

  }

下面这一段是 sychronized 加锁,外围中的外围,能够发现很多有意思的中央:

1) 从正文能够看出,锁会有收缩过程,对象头会记录锁的相干信息。

2) Atomic::cmpxchg_ptr 体现了 ompare and exchange (CAS)操作,是轻量级锁。

3) mark->has_locker() && THREAD->is_lock_owned((address)mark->locker()体现了 synchronized 是可重入锁

4) 最初的 ObjectSynchronizer::inflate 意思为收缩为重量级锁。

C++ 的代码有很多细节和常识,你开始学习的时候不要想着全副搞清楚,肯定要有之前学到的思维,先脉络后细节。搞清楚脉络再说钻研细节的局部。

所以,通过初步看过 synchronized 的 HotSpot C++ 代码实现,重点的脉络就是锁降级的过程和原理,接下来重点剖析一下这个过程。

synchronized 锁降级的过程

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>synchronized 锁降级的过程 </span></h3></div>

后面通过从字节码层面到 JVM 层面初步理解了 synchronized 的实现,联合之前说的 sychronized 的锁的几种类型。最终能够剖析进去 synchronized 锁会有一个降级的过程。过程如下图所示:

这个图十分重要,大家肯定要牢记住。下一节会破费整整一节来讲在这个图。

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0