線程&Synchronized

概要 : 線程Thread 進程 纖程/協程Fiber

在這裏插入圖片描述

圖 1 馮諾依曼計算機體系結構

進程與線程的區別

  • 進程:操作系統資源分配的基本單位
  • 線程:CPU執行的基本單位,也是一個進程裏面最小的執行單元

一個CPU(核)在同一時刻可以執行幾個線程?

​ 除非有超線程存在,否則1個

線程切換

ALU: 運算單元

Register: 寄存器

PC: 程序計數器

Cache: 緩存

線程切換的時候,需要把寄存器和程序計數器中的內容保存(保護線程),然後在切換到其他線程執行,再切換回來。

JVM 是運行在操作系統之上的一個軟件,線程分爲操作系統級別的線程以及JVM級別的纖程。JVM規範中並未規定JVM中的纖程和操作系統的線程如何對應,但是 HotSpot 中的實現是一一對應的。

synchronized 關鍵字在 jdk1.2 之前是重量級鎖,所謂重量級,就是由操作系統來直接管理加鎖以及釋放鎖的過程。後來經過一系列升級之後,synchronized 底層實現了 無鎖 -> 偏向鎖 -> 自旋鎖 -> 重量級鎖 的鎖升級過程。

​ 如果不是操作系統直接管理,可以由虛擬機來管理,JVM 自己來管理 保護線程 的操作,多個纖程對應一個線程。在運算簡單但是需要很多線程來運算的場景,纖程的實現比直接使用線程快很多。

Synchronized 如何實現

介紹 synchronized 之前,要先了解對象在內存中的存儲形式

在這裏插入圖片描述

圖 2 java 對象結構

對象的幾個部分的作用:

  1. 對象頭中的Mark Word(標記字)主要用來表示對象的線程鎖狀態,另外還可以用來配合 GC 以及存放該對象的 hashCode

  2. Klass Word 是一個指向方法區中 Class 信息的指針,意味着該對象可隨時知道自己是哪個 Class 的實例;

  3. 數組長度也是佔用64位(8字節)的空間,這是可選的,只有當本對象是一個數組對象時纔會有這個部分;

  4. 對象體是用於保存對象屬性和值的主體部分,佔用內存空間取決於對象的屬性數量和類型

  5. 對齊字是爲了減少堆內存的碎片空間,提高內存讀寫效率。將整個對象佔用的空間對齊爲能被8字節整除

一、MarkWord標記字

在這裏插入圖片描述

  1. 使用2bit標誌位來表示不同的狀態,無鎖 -> 偏向鎖 -> 自旋鎖 -> 重量級鎖
  2. 分代年齡: 佔用4bit,所以最大值是15,-XX:MaxTenuringThreshold 的最大值爲15;
  3. identity_hashcode:31位的對象標識hashCode,採用延遲加載技術。調用方法System.identityHashCode() 計算,並會將結果寫到該對象頭中。當對象加鎖後(偏向、輕量級、重量級),MarkWord 的字節沒有足夠的空間保存 hashCode,因此該值會移動到管程Monitor
  4. thread:持有偏向鎖的線程ID。
  5. epoch:偏向鎖的時間戳。
  6. ptr_to_lock_record:輕量級鎖狀態下,指向棧中鎖記錄的指針。
  7. ptr_to_heavyweight_monitor:重量級鎖狀態下,指向對象監視器Monitor的指針。
重量級鎖的問題

​ 會造成線程排隊(串行執行),且會使CPU用戶態和核心態之間頻繁切換,所以代價高、效率低。爲了提高效率,不會一開始就使用重量級鎖,JVM在內部會根據需要,按如下步驟進行鎖的升級:

  1. 初期鎖對象剛創建時,還沒有任何線程來競爭,對象的Mark Word是上圖中第一種情形,這偏向鎖標識位是0,鎖狀態01,說明該對象處於無鎖狀態(無線程競爭它)。

  2. 當有一個線程來競爭鎖時,先用偏向鎖,表示鎖對象偏愛這個線程。

  • 默認情況 偏向鎖有個時延,默認是4秒:
  • 因爲JVM虛擬機自己有一些默認啓動的線程,裏面有好多sync代碼,這些sync代碼啓動時就知道肯定會有競爭,如果使用偏向鎖,就會造成偏向鎖不斷的進行鎖撤銷和鎖升級的操作,效率較低。

這個線程要執行這個鎖關聯的任何代碼,不需要再做任何檢查和切換,這種競爭不激烈的情況下,效率非常高。這時Mark Word會記錄自己偏愛的線程的ID(加鎖的過程),把該線程當做自己的熟人。如上圖中第二種情形。

  1. 當有兩個線程開始競爭這個鎖對象,情況發生變化了,不再是偏向(獨佔)鎖了,鎖會升級爲輕量級鎖,兩個線程公平競爭,哪個線程先佔有鎖對象並執行代碼,鎖對象的 Mark Word 就執行哪個線程的棧幀中的鎖記錄。如上圖第3.1種情形。

  2. 如果競爭的這個鎖對象的線程更多,導致了更多的切換和等待,JVM 會把該鎖對象的鎖升級爲重量級鎖,這個就叫做同步鎖,這個鎖對象 Mark Word 再次發生變化,會指向一個監視器對象,這個監視器對象用集合的形式,來登記和管理排隊的線程。如上圖第3.2種情形。

  3. 鎖標誌位爲 (1,1) 時,表示該對象被 GC 標記過了

    -XX:BiasedLockingStartupDelay=0
    

如果設定上述參數 new Object () 將直接打開 101 偏向鎖 -> 線程ID爲0 -> Anonymous BiasedLock
打開偏向鎖,new出來的對象,默認就是一個可偏向匿名對象101

JOL工具

jol是一款方便地打印對象內存內容的工具

// https://mvnrepository.com/artifact/org.openjdk.jol/jol-core
compile group: 'org.openjdk.jol', name: 'jol-core', version: '0.10'
public class JolTest {
    private static AtomicInteger num = new AtomicInteger(0);
    private static ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<>();

    public static void main(String[] args) {
//        Object o = new Object();
//        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        num.incrementAndGet();
        map.put("abc", 100L);

        try {
            // sleep 4秒之後偏向鎖啓動
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        User u = new User();
        u.setId(12);
//        System.out.println(ClassLayout.parseInstance(u).toPrintable());
        u.setCode(Long.MAX_VALUE);
        u.setName("張三");
        System.out.println(ClassLayout.parseInstance(u).toPrintable());

        // 無鎖狀態
        synchronized (u) {
            System.out.println(ClassLayout.parseInstance(u).toPrintable());
        }

        System.gc();
        System.out.println(ClassLayout.parseInstance(u).toPrintable());

    }
}

synchronized 代碼實現

  1. Java 層級
synchronized (o) 
  1. 字節碼層級
monitorenter moniterexit
  1. JVM層級(Hotspot
package com.mashibing.insidesync;
public class T01_Sync1 {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}
com.mashibing.insidesync.T01_Sync1$Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4   (object header)  05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4   (object header)  00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4   (object header)  49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.mashibing.insidesync.T02_Sync2$Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4   (object header)  05 90 2e 1e (00000101 10010000 00101110 00011110) (506368005)
      4     4   (object header)  1b 02 00 00 (00011011 00000010 00000000 00000000) (539)
      8     4   (object header)  49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes tota

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

synchronizer.cpp

revoke_and_rebias

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

inflate 方法:膨脹爲重量級鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章