美團追魂連環7問

經過前幾天美團一面的體驗感覺,明顯感覺到,美團面試官對於基礎的癡迷和追問,不是你看點面經什麼的就可以糊弄過去的,與面試管的暢聊也激發了我想要進一步細緻學習的興趣。無意間看到馬士兵老師的這個課程,寫成博客記錄一下
課程資源

關於Object o = new Object()

1:對象的創建過程(半初始化)

來自課程截圖

2:DCL與volatile問題(指令重排)

DCL:Double Check Lock
是在單例模式中,我們在getInstance()方法中判斷,如果instance爲null,則創建單例對象。此時,如果兩個對象同時調用該方法,則instance==null判斷時,都爲null,則會創建出兩個示例對象,就不符合單例模式了,即出現了線程不安全。爲了保證線程安全,我們需要使用synchronized,添加鎖機制。若直接修飾getInstance()方法,如果方法裏的一些操作不需要加鎖,則會降低代碼的併發性,即鎖的粒度太大,我們這裏只需要在創建實例對象的時候,添加鎖就可保證線程安全。
第一次判斷instance爲null,如果爲空,就申請鎖,創建對象。
(如果此時,線程A獲得鎖,進入創建實例對象,線程B等待,當A釋放鎖,B獲得鎖,此時instance已經不爲null,但是如果我們沒有進行二次判斷,這時,就會再次創建新的實例對象,就不符合單例的場景了)
所以我們需要在申請到鎖後,再次判斷instance是否爲null。
代碼如下:

package singleton;
/*
 * (1)構造器私有化
 * (2)不自行創建,只定義一個靜態變量
 * (3)提供一個靜態方法,獲取實例對象
 * Double Check Lock
 */
public class Singleton5 {
	//要不要加volatile
	//volatile:作用1 線程可見性   作用2 禁止指令重排序
    private static volatile Singleton5 instance;
    private Singleton5() {}
//爲了線程安全,可以對getInstanc()上鎖,但是鎖的範圍(粒度)太大了,如果方法裏的一些操作不需要加鎖。
	public static Singleton5 getInstance(){
		//添加一個同步鎖,保證線程安全
		//爲了保證線程安全得同時,提供效率,把對instance==null得判斷放在鎖得外面
		if(instance==null) {
			synchronized (Singleton5.class) {
				//雙重檢查
				if(instance==null) {
//測試當兩個線程,先後進入到這裏,休眠,那麼就會創建兩個實例,所以這個例子存在線程安全問題
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					instance = new Singleton5();
				}	
			}
		}
		return instance;
	}	
}

再思考一個問題:
DCL單例到底需不需要volatile-----需要
volatile
作用1 :線程可見性
作用2 : 禁止指令重排序
指令重排序的意思就說,若指令1讀內存很慢,cpu不會一直等待,而是先去執行與指令1無相關性的指令2,這樣按理指令的執行順序是1->2,而實際是2->1.這就是指令重排序。
在這裏插入圖片描述
如果不用volatile 禁止指令重排序(底層實現是會對這塊區域上下都加內存屏障),那麼當線程1半初始化後,發生指令重排,先執行astore使得t去指向半初始化的對象,此時,線程2在進行排隊判斷的時候,就會去使用這個半初始化的對象,例如,成員變量,本應該是8,這時候是0.,從而出現錯誤。在這裏插入圖片描述

3:對象在內存中的存儲佈局(對象與數組的存儲不同)

在這裏插入圖片描述
mark word+classpointer是對象頭
mark word(8字節):存放有鎖狀態,分代年齡,hashcode等
classpointer(4字節):例如T t = new T();那麼classpointer會指向T.class
instance data(?字節):放的是該類型實例的成員變量,若成員變量是一個int 一個string ,則爲(4+4字節),此時padding 補4
padding(?字節):若前3項佔用的空間不夠8整除,會用來補齊(cpu總線在讀取數據內容的時候,是希望讀取的內容和它對應的位數是一一對應的,纔是最好。對於64位的計算機,它的寄存器也是64位,就希望我們讀到數據的大小能剛好被8整除,可以提高效率)

4:對象頭具體包括什麼(markword classpointer)synchronized鎖信息

包括:鎖信息,GC信息,hashcode
在這裏插入圖片描述

5:對象怎麼定位(直接間接)

直接:直接指針
間接:句柄方式
在這裏插入圖片描述
句柄方式:
優點:對象小,垃圾回收時不用頻繁改動t
缺點:兩次訪問

6:對象怎麼分配(棧上-線程本地-Eden -Old)

在這裏插入圖片描述
棧上分配原則:沒有逸出(別的方法不用這個對象),標量替換(直接存兩個成員變量)
如果在棧上分配,結束時,直接出棧,不需要GC的介入,效率高
TLAB(Threadlocal Allocation Buffer 線程本地分配緩衝區)

7: Object o = new Object()在內存中佔多少字節

主要是要看是否開啓壓縮:
壓縮有兩種:1 壓縮classpointer 2:壓縮普通對象指針
o爲普通對象指針(OOPS):4個字節
16+4 = 20個字節

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