旁白:通常的面試都是從最簡單基本的問題開始。html
面試官:請在黑板上寫出一個線程安全的單例模式的例子。java
面試者:面試
其實線程安全的實現有不少種,根據業務場景能夠new一個實例做爲私有靜態成員變量,這樣程序一啓動,實例就生成,私有化構造函數,利用公用的靜態函數getInstance返回實例。這種預加載的是能保證線程安全的可是若是不是肯定會被使用,會形成內存的浪費,因此能夠將實例放到私有靜態類中做爲成員變量。下面只寫一種利用鎖機制來保證的懶加載方法。算法
public
class
Singleton {
private
volatile
static
Singleton singleton;
private
Singleton (){}
public
static
Singleton getSingleton() {
if
(singleton ==
null
) {
synchronized
(Singleton.
class
) {
if
(singleton ==
null
) {
singleton =
new
Singleton();
}
}
}
return
singleton;
}
}
|
旁白:從這個例子上我能想到的知識點主要有三個編程
☆ volatile 關鍵字,可深刻到Java VM內存相關緩存
☆ synchronized 關鍵字,可深刻到Java鎖機制,高併發相關安全
☆ new 關鍵字,可深刻到Java VM類加載機制相關數據結構
可是面試官一開始可能要先考察一下面試者是否真的理解本身寫的代碼多線程
面試官:你寫的這個程序是怎麼保證線程安全的?併發
面試者:將類的構造方法私有起來,外部調用進行初始化的時候只能經過調用getSingleton這個靜態方法來得到實例,靜態方法是整個Java虛擬機中只有一個實例。在建立的時候首先進行非空判斷,這時候若是實例不存在,對整個類進行加鎖同步,爲了不過程當中非空狀態的改變,同步塊內再進行一次判斷,若是不存在實例則建立實例返回。使用volatile關鍵字,下次訪問這個方法就能直接看到實例的最新非空狀態,直接返回實例。
面試官:volatile 起到了什麼做用?
面試者:volatile這個英文單詞意思是易變的,用在多線程中來同步變量。Java的對象都是在內存堆中分配空間。可是Java有主內存和線程本身獨有的內存拷貝。對於沒有volatile修飾的局部變量,線程在運行過程當中訪問的是工做內存中的變量值,其修改對於主內存不是當即可見。而volatile修飾的值在線程獨有的工做內存中無副本,線程直接和主內存交互,修改對主內存當即可見。
面試官:synchronized起到了什麼做用?
面試者:鎖定對象,限制當前對象只能被一個線程訪問。
面試官:synchronized裏你傳Singleton.class這個參數,起到什麼做用,換成別的行不行?
面試者:對當前類加鎖,使得這個代碼塊一次只能被一個線程訪問。這裏Singleton.class能夠換成一個常量字符串或者本身定義一個內部靜態Object。
面試官:那傳Singleton.class,常量字符串,本身定義一個內部靜態Object有區別嗎?
面試者:由於這是一個靜態方法,至關於一個概念上的類鎖,因此在這裏起到的效果是同樣的。可是若是是原型模式,或者直接每一個類都是new出來的,實例不一樣的話,在一個非靜態方法里加這三種鎖,這時是一個對象鎖,由於Singleton.class或者是靜態的一個Object或者是JVM只存一份的字符串常量,這些對象線程間是共享的,會對全部的實例的同步塊都加同一把鎖,每一個實例訪問到此對象的同步代碼塊都會被阻塞。可是若是這時synchronized的參數是this,或者是內部new出來的一個內部非靜態Object,則各個實例擁有不一樣的鎖,訪問同一個代碼相同同步塊也是互不干擾。只有實例內部使用了同一個對象鎖纔會同步等待。
面試官:那你知道synchronized關鍵字實現同步的原理嗎?
面試者:synchronized在Java虛擬機中使用監視器鎖來實現。每一個對象都有一個監視器鎖,當監視器鎖被佔用時就會處於鎖定狀態。
線程執行一條叫monitorenter的指令來獲取監視器鎖的全部權。若是此監視器鎖的進入數爲0,則線程進入並將進入數設置爲1,成爲線程全部者。若是線程已經擁有該鎖,由於是可重入鎖,能夠從新進入,則進入數加1.若是線程的監視器鎖被其餘線程佔用,則阻塞直到此監視器鎖的進入數爲0時才能進入該鎖。
線程執行一條叫monitorexit的指令來釋放全部權。執行monitorexit的必須是線程的全部者。每次執行此指令,線程進入數減1,直到進入數爲0。監視器鎖被釋放。
面試官:你剛纔提到的可重入鎖是什麼概念,有不可重入鎖嗎?
面試者:我說的可重入鎖是廣義的可重入鎖,固然jdk1.5引入了concurrent包,裏面有Lock接口,它有一個實現叫ReentrantLock。廣義的可重入鎖也叫遞歸鎖,是指同一線程外層函數得到鎖以後,內層還能夠再次得到此鎖。可重入鎖的設計是爲了不死鎖。sun的corba裏的mutex互斥鎖是一種不可重入鎖的實現。自旋鎖也是一種不可重入鎖,本質上是一種忙等鎖,CPU一直循環執行"測試並設置"直到可用並取得該鎖,在遞歸的調用該鎖時必然會引發死鎖。另外,若是鎖佔用時間較長,自旋鎖會過多的佔用CPU資源,這時使用基於睡眠原理來實現的鎖更加合適。
面試官:你剛纔提到了concurrent包,它裏面有哪些鎖的實現?
面試者:經常使用的有ReentrantLock,它是一種獨佔鎖。ReadWriteLock接口也是一個鎖接口,和Lock接口是一種關聯關係,它返回一個只讀的Lock和只寫的Lock。讀寫分離,在沒有寫鎖的狀況下,讀鎖是無阻塞的,提升了執行效率,它是一種共享鎖。ReadWriteLock的實現類爲ReentrantReadWriteLock。ReentrantLock和ReentrantReadWriteLock實現都依賴於AbstractQueuedSynchronizer這種抽象隊列同步器。
面試官:鎖還有其餘維度的分類嗎?
面試者:還能夠分爲公平鎖和非公平鎖。非公平鎖是若是一個線程嘗試獲取鎖時能夠獲取鎖,就直接成功獲取。公平鎖則在鎖被釋放後將鎖分配給等待隊列隊首的線程。
面試官:AQS是什麼?
面試者:AQS是一個簡單的框架,這個框架爲同步狀態的原子性管理,線程的阻塞和非阻塞以及排隊提供了一種通用機制。表現爲一個同步器,主要支持獲取鎖和釋放鎖。獲取鎖的時候若是是獨佔鎖就有可能阻塞,若是是共享鎖就有可能失敗。若是是阻塞,線程就要進入阻塞隊列,當狀態變成可得到鎖就修改狀態,已進入阻塞隊列的要從阻塞隊列中移除。釋放鎖時修改狀態位及喚醒其餘被阻塞的線程。
AQS本質是採用CHL模型完成了一個先進先出的隊列。對於入隊,採用CAS操做,每次比較尾節點是否一致,而後插入到尾節點中。對於出隊列,由於每一個節點緩存了一個狀態位,不知足條件時自旋等待,直到知足條件時將頭節點設置爲下一個節點。
面試官:那知道這個隊列的數據結構嗎?
面試者:這個隊列是用一個雙向鏈表實現的。
面試官:你剛纔提到AQS是一種通用機制,那它還有哪些應用?
面試者:AQS除了剛纔提到的可重入鎖ReentrantLock和ReentrantReadWriteLock以外,還用於不可重入鎖Mutex的實現。java併發包中的同步器如:Semphore,CountDownLatch,FutureTask,CyclicBarrier都是採用這個機制實現的。
旁白:既然問到了併發工具包中的東西,每一個均可以引出一堆,可是基本原理已經問出來了,其餘的問下去沒什麼意思。轉向下一個問題。
面試官:你黑板上寫的實例是經過new對象建立出來的,還可不能夠採用別的方法來建立對象呢?
面試者:還可使用class類的newInstance方法,Constructor構造器類的newInstance方法,克隆方法和反序列法方法。
面試官:兩種newInstance方法有沒有區別?
面試者:
☆ Class類位於java的lang包中,而構造器類是java反射機制的一部分。
☆ Class類的newInstance只能觸發無參數的構造方法建立對象,而構造器類的newInstance能觸發有參數或者任意參數的構造方法來建立對象。
☆ Class類的newInstance須要其構造方法是共有的或者對調用方法可見的,而構造器類的newInstance能夠在特定環境下調用私有構造方法來建立對象。
☆ Class類的newInstance拋出類構造函數的異常,而構造器類的newInstance包裝了一個InvocationTargetException異常。
Class類本質上調用了反射包構造器類中無參數的newInstance方法,捕獲了InvocationTargetException,將構造器自己的異常拋出。
面試官:類加載的時候,本身定義了一個類和java本身的類類名和命名空間都同樣,JVM加載的是哪個呢?
面試者:調用的是java自身的,根據雙親委派模型,最委派Bootstrap的ClassLoader來加載,找不到纔去使用Extension的ClassLoader,還找不到纔去用Application的ClassLoader,這種機制利於保證JVM的安全。
面試官:你剛纔提到的java的反射機制是什麼概念?
面試者:java的反射機制是在運行狀態中,對於任何一個類,都可以知道它全部的屬性和方法;對於任何一個對象,都可以調用它的任何一個方法和屬性。這種動態的獲取信息和動態調用對象的方法的功能就是java的反射機制。它是jdk動態代理的實現方法。
面試官:java還有沒有其餘的動態代理實現?
面試者:還有cglib動態代理。
面試官:這兩種動態代理哪一個比較好呢?
面試者:AOP源碼中同時使用了這兩種動態代理,由於他們各有優劣。jdk動態代理是利用java內部的反射機制來實現,在生成類的過程當中比較高效,cglib動態代理則是藉助asm來實現,能夠利用asm將生成的類進行緩存,因此在類生成以後的相關執行過程當中比較高效。可是jdk的動態代理前提是目標類必須基於統一的接口,因此有必定的侷限性。
旁白:面試者都已經提到AOP了,那麼接下來橫向,縱向,怎樣都能問出一大堆問題,就不贅述。基於上面問題,讀者也能夠本身畫出一棵知識樹,而後就能找到能對答如流的終極方案:就是基本都沒超過《深刻理解java虛擬器》《java併發編程實踐》這兩本書,大學學過的《數據結構與算法》《編譯原理》掌握的好也能夠在面試中加分哦。
原文地址
https://www.cnblogs.com/xiexj/p/6845029.html?from=singlemessage