JDK源碼那些事兒之萬物之源Object

從這篇文章開始進行AQS相關的學習,若是你還不明白什麼是AQS,能夠先去了解下,因爲涉及的源碼衆多,筆者會一步一步進行深刻說明整理,在學習AQS前,有不少基礎知識是須要咱們先去了解的,好比本文所說的Objectjava

前言

JDK版本號:1.8.0_171

我相信不少開發者都沒有徹底去了解過Object類,只是使用的時候知道而已,其實其中仍是有不少能夠學習的知識算法

Java中的父類也稱爲超類,而Java是面向對象的語言,面向對象的基礎就是類。Java中全部類都有一個共同的祖先Object類,Object類是惟一沒有父類的類,Object類引用變量能夠存儲任何類對象的引用。那麼做爲萬物之源的Object類不知道你們有沒有去看一看其中的源碼呢?設計模式

爲何先說Object類呢?由於其中有部分方法涉及到了多線程等待通知機制,也就是線程間通訊協做的一種實現方式,是咱們深刻AQS前應該先了解的知識,方便後期對比,加深理解數據結構

Object

接下來咱們一塊兒看一看Object的源碼實現多線程

registerNatives

以前的源碼分析中已經說起到了這個方法的做用,這裏就簡單再說明下,其主要做用是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦。函數的執行是在靜態代碼塊中執行的,在類首次進行加載的時候執行app

private static native void registerNatives();
    static {
        registerNatives();
    }

getClass

返回運行時對象的Class類型對象,也就是運行時實際的Class類型對象,一樣是native方法,反射也是使用其來實現的框架

public final native Class<?> getClass();

hashCode/equals

hashCode返回對象的哈希碼,是一個整數,這個整數是對象根據特定算法計算出來的一個散列值(hash值),而equals方法直接使用==進行比對,也就是比較對象的地址,默認的約定是相同的對象必須具備相同的哈希碼。子類能夠重寫hashCode方法,可是重寫後euqals方法一般也須要重寫,這裏就提出了那個經典的問題:爲何hashCode和euqals重寫其中一個方法,另外一個方法也建議重寫?jvm

首先你應該知道的是這兩個方法具備如下特性:函數

  • 兩個對象經過equals()判斷爲true,則這兩個對象的hashCode()返回值也必須相同
  • 兩個對象的hashCode()返回值相同,equals()比較不必定須要爲true,能夠是不一樣對象

爲何要這麼定義呢?其實咱們聯想下HashMap就明白了,HashMap的底層數據結構就是哈希表,經過key的hashCode進行散列,可是這裏咱們明白還須要解決hash衝突,這裏就經過對象key的equals完成對比,咱們比較時是須要比較對象的屬性值的,而不是對象地址,這也是咱們爲何一般使用String對象來做爲key,由於String已經對hashCode和equals進行了重寫,若是咱們本身寫了一個對象,沒有進行重寫,做爲HashMap的key,你能夠想一想會出現什麼狀況?源碼分析

public native int hashCode();

    public boolean equals(Object obj) {
        return (this == obj);
    }

clone

clone方法是個本地方法,效率很高。當須要複製一個相同的對象時通常都經過clone來實現,而不是new一個新對象再把原對象的屬性值等複製給新對象。Object類中定義的clone方法是protected的,必須在子類中重載這個方法才能使用。clone方法返回的是個Object對象,必須進行強制類型轉換才能獲得須要的類型。在設計模式中原型模式就是經過clone方法來完成的,有興趣能夠去查找資料進行了解

實現clone方法須要繼承Cloneable接口,不繼承會拋出CloneNotSupportedException異常

protected native Object clone() throws CloneNotSupportedException;

toString

能夠看到默認的toString實現,這也就是咱們未重寫時默認的輸出,通常而言咱們建立對象時都會進行重寫,便於日誌輸出

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

notify

咱們能夠看到做者的註釋:

喚醒正在此對象的monitor上等待的單個線程,若是有任何線程在該對象上等待,則選擇其中一個喚醒。該選擇是任意的,而且能夠根據實現狀況進行選擇。線程經過調用wait方法在對象的monitor上等待。在當前線程放棄該對象上的鎖以前,喚醒的線程將沒法繼續。喚醒的線程將以一般的方式與任何其餘可能正在主動競爭以與此對象進行同步的線程競爭。此方法只能由做爲該對象的monitor的全部者的線程調用。線程經過synchronized成爲對象monitor的全部者,也就是獲取對象的鎖,同一時刻只有一個線程能夠得到對象的鎖

簡單說明下,通知可能等待該對象的對象鎖的其餘線程。由JVM隨機挑選一個處於wait狀態的線程

  • 在調用notify()以前,線程必須得到該對象的對象級別鎖
  • 執行完notify()方法後,不會立刻釋放鎖,要直到退出synchronized代碼塊,當前線程纔會釋放鎖
  • notify()一次只隨機通知一個線程進行喚醒
public final native void notify();

notifyAll

喚醒正在此對象的monitor上等待的全部線程,在當前線程放棄對該對象的鎖定以前,喚醒的線程將沒法繼續。喚醒的線程將以一般的方式與可能正在競爭在此對象上進行同步的任何其餘線程競爭,此方法只能由擁有該對象的monitor的全部者的線程調用

和notify功能差很少,只不過是使全部正在等待線程池中等待同一共享資源的所有線程從等待狀態退出,進入可運行狀態,讓它們競爭對象的鎖,只有得到鎖的線程才能進入就緒狀態

public final native void notifyAll();

wait

使當前線程等待,直到另外一個線程在對象上調用notify方法或notifyAll方法喚醒等待線程,能夠等待指定的時間,在等待過程當中能夠被中斷,這裏拋出InterruptedException異常

public final native void wait(long timeout) throws InterruptedException;

注意,在調用此方法前,當前線程必須先擁有對象的鎖才能進行,註釋上也說明了其使用方法:

synchronized (obj) {
       while (<condition does not hold>)
           obj.wait(timeout);
       ... // Perform action appropriate to condition
   }

finalize

當垃圾回收肯定再也不有對該對象的引用時,由垃圾回收器在對象上調用。子類覆蓋finalize方法以處置系統資源或執行其餘清除。finalize的通常約定是,當Java虛擬機肯定再也不有任何手段可使還沒有死亡的任何線程訪問該對象時(除非因爲執行操做而致使),調用finalize。簡單點說就是在垃圾回收以前調用,可是不推薦使用,瞭解下就好

protected void finalize() throws Throwable { }

synchronized

Object對象中wait/notify/notifyAll就是線程間進行通訊協做的一種方式,也就是一般的等待喚醒機制,只是在使用時咱們須要先獲取對應的對象鎖才能進行調用,通常而言經過synchronized完成

那麼,synchronized是如何實現的呢?咱們經過代碼來看看,方法上添加了synchronized和對代碼塊添加synchronized是不一樣的

public synchronized void synchronizedMethod(){
        System.out.println("synchronizedMethod");
    }

    public void synchronizedBlock(){
        synchronized(this){
            System.out.println("synchronizedBlock");
        }
    }

咱們反編譯下看看,能夠看到在方法上的flags上添加了ACC_SYNCHRONIZED標識,而代碼塊上是多了monitorentermonitorexit兩個jvm指令集,那麼你應該明白了synchronized是經過JVM底層來實現的,既然不是Java API層面上的實現,那麼先了解就好,咱們的重點學習部分在於Java API層面上的同步框架實現——AQS

public synchronized void synchronizedMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String synchronizedMethod
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 14: 0
        line 15: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/example/demo/SynchronizedTest;

  public void synchronizedBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #5                  // String synchronizedBlock
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 18: 0
        line 19: 4
        line 20: 12
        line 21: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/example/demo/SynchronizedTest;

總結

本文簡單介紹了萬物之源Object的源碼和其中的方法,重點關注下wait/notify/notifyAll,其須要結合synchronized來完成線程間通訊協做,經過反編譯文件理解JVM對其進行的處理,以後會繼續進行AQS的相關學習整理

以上內容若有問題歡迎指出,筆者驗證後將及時修正,謝謝

相關文章
相關標籤/搜索