1. 導(dǎo)讀
接上一篇的分享, 我們一起看一下Object類中剩余的6個方法:
1.1 toString();
1.2 notify();
1.3 notifyAll();
1.4 wait();
1.5 finalize();
1.6 registerNatives();
2. toString方法
toString方法是我們比較常用的方法, 在Object中的默認實現(xiàn)返回一個 類名+'@'+hasCode的16進制拼接的字符串;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
注意到toString方法是沒有被final修飾的, 證明他可以被子類重寫;
劃重點:
.1 我們可以根據(jù)自身需求重寫toString方法, 默認實現(xiàn)是返回類名+'@'+hashCode的16進制拼接而成的字符串;
.2 我們調(diào)用System.out.println(obj), 默認調(diào)用的就是obj的toString方法;
3. notify, notifyAll 和 wait方法
把這三個方法放一起是因為這三個方法是配套的, 用來實現(xiàn)JAVA多線程的協(xié)作; 既然是多線程相關(guān)的方法, 為什么會在Object這個類中呢? 這個是因為調(diào)用這三個方法前提都是需要在synchronized修飾的同步代碼塊中, 而synchronized鎖的實現(xiàn)是基于對象(Object)監(jiān)視器的;
那么這三個方法之間是如何協(xié)作的? 每個方法又具體干了什么呢?
3.1 wait: 在同步代碼塊中調(diào)用該方法時, 當前線程立即釋放鎖并等待, 直到有其他線程調(diào)用notify/notifyAll或超時等待時, 才會去再次競爭鎖, 成功后繼續(xù)執(zhí)行下面的邏輯;
public final native void wait(long timeout) throws InterruptedException;
wait方法被final, native修飾, 證明他是不可被重寫的原生方法; 該方法在等待的時候, 有其他線程打斷了他的等待, 那么他會拋出InterruptedException并退出等待;
Object類中還有wait(), wait(long timeout, int nanos)這兩種wait的實現(xiàn), 但其實都是調(diào)用的wait(long timeout);
3.2 notify: 線程A在同步代碼塊中調(diào)用該方法時, 會隨機地喚醒一個等待在該對象鎖上的線程B, 注意這時候喚醒的線程B還沒有持有鎖, 必須要等到線程A釋放鎖后才能持有該把鎖;
當線程在A對象的同步代碼塊中執(zhí)行B對象的notify時, 會拋出IllegalMonitorStateException;如果沒有這個限制, 我們想想會發(fā)生什么情況;
拿最常用的生產(chǎn)者消費者舉例:
1. 消費者消費(notify生產(chǎn)者);
2. 當貨物不存在時等待生產(chǎn)者生產(chǎn)(wait);
3. 生產(chǎn)者生產(chǎn)貨物(notify消費者);
4. 當貨物沒被消費時等待(wait);
我們期望的是 1-->2-->3-->4-->1-->2...這樣的順序;
但是如果消費者和生產(chǎn)者持有的是兩把不同的對象鎖, 那么當消費者notify時, 因為生產(chǎn)者等待在另一把鎖上, 導(dǎo)致無法喚醒生產(chǎn)者, 那么就會導(dǎo)致:1-->2-->4,生產(chǎn)者和消費者會同時阻塞;
所以為了消除這種競態(tài)條件, 在A對象的同步代碼塊中, 只能調(diào)用A對象的notify方法, 否則就會拋出IllegalMonitorStateException;
3.3 notifyAll: 線程A在同步代碼塊中調(diào)用該方法時, 會喚醒所有等待在該鎖上的線程, 同樣的, 這些喚醒的線程只有在線程A釋放鎖以后, 才能再次競爭該把鎖, 競爭到鎖的線程繼續(xù)執(zhí)行, 其他的線程繼續(xù)等待;
如果調(diào)用的線程不是該把鎖的持有者, 那么也會拋出IllegalMonitorStateException;
public final native void notify();
public final native void notifyAll();
nofity 和 notifyAll都是不可重寫的原生方法, 雖然這兩個方法沒有顯式的拋出IllegalMonitorStateException這個異常, 但是當競態(tài)條件產(chǎn)生時, IllegalMonitorStateException這個異常自然就出現(xiàn)了;
劃重點:
.1 執(zhí)行notify | notifyAll時, 喚醒的線程并不會立即持有鎖, 故而會形成假喚醒的情況, 那么在寫wait方法的時, 推薦使用以下方式:
synchronized(lock) {
while(!condition) {
lock.wait();
}
//doSomething;
}
當條件不滿足時, 該線程還需繼續(xù)等待;
.2 執(zhí)行wait | notify | notifyAll的對象, 必須與synchronized鎖住的對象是同一個, 否則會形成競態(tài)條件導(dǎo)致IllegalMonitorStateException異常的產(chǎn)生;
.3 java多線程這塊內(nèi)容較多, 這期只是簡略的介紹下這三個方法, 后面會有一系列的文章專門分享多線程相關(guān)的內(nèi)容;
4. finalize方法
protected void finalize() throws Throwable { }
java的內(nèi)存管理依賴于JVM實現(xiàn)的GC(Garbage Collection)機制來實現(xiàn)內(nèi)存的回收, GC相關(guān)的內(nèi)容后面再展開; JVM在進行GC時, 如果這個對象需要被回收, 會先判斷該方法是否有被重寫, 若未重寫, 則直接回收該對象內(nèi)存空間;
反之則判斷該對象的finalize是否被執(zhí)行過, 如果沒有執(zhí)行過, 會先放入一個隊列中, 由低優(yōu)先級的線程去執(zhí)行該對象的finalize方法, 執(zhí)行完畢后再判斷該對象是否需要回收;
如果該對象已經(jīng)執(zhí)行過一遍finalize方法了, 直接回收對象的內(nèi)存空間;
上圖就是對GC執(zhí)行回收對象finaliz方法時對象狀態(tài)變化的過程;
劃重點:
.1 重寫了finalize方法后, 在對象的整個生命周期中GC只會執(zhí)行一次finalize方法;
5. registerNatives方法
最后來看一下registerNatives方法, 可能看過源碼的同學(xué)都知道這個方法是Object類中的第一個方法, 我把他放到最后將的原因是和他的功能相關(guān);
private static native void registerNatives();
static {
registerNatives();
}
首先應(yīng)該關(guān)注到的是static代碼塊, 靜態(tài)代碼塊是在類加載時就會執(zhí)行的, 這個代碼塊中只是調(diào)用了registerNatives方法;
再看到registerNatives方法:
5.1 static: 這是個靜態(tài)方法, 因為靜態(tài)只能調(diào)用靜態(tài), 也就是只有靜態(tài)方法才能在靜態(tài)代碼塊中直接調(diào)用;
5.2 native: 原生方法, 他是由C實現(xiàn)的;
5.3 看到方法名, 我們可以猜到他是注冊O(shè)bject類中的原生方法的, 實現(xiàn)java中聲明的native方法與C實現(xiàn)函數(shù)的綁定;
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
這是OpenJDK6對Object中native方法的綁定, Java_java_lang_Object_registerNatives這樣函數(shù)可直接調(diào)用java中native函數(shù), 通過上面的代碼可以清晰的看到registerNatives實現(xiàn)了method中native方法的綁定;
同時也可以看到methods中是沒有g(shù)etClass這個方法, 自然可以猜到他也是采用規(guī)定函數(shù)名稱直接調(diào)用的方式實現(xiàn)綁定的(Java_java_lang_Object_getClass);
JNI這里就不做展開, 感興趣的可以閱讀下
Java? Native Interface: Programmer's Guide and Specification;
至此, Object類中的所有方法的解析已經(jīng)告一段落了, 如有錯誤之處, 歡迎指正;