轉 http://www.cnblogs.com/xiexj/p/6845029.html
看了左瀟龍的《回答阿里社招面試如何准備,順便談談對於Java程序猿學習當中各個階段的建議》這篇文章,在想一個問題,從一個最簡單的問題入手究竟能把問題問多深?下面就模擬一場面試問答,要是我是面試官,大概就只能問到下面的深度了。
LZ的風格,照例跑會兒題。話說周末跟兒子去超市買了一堆零食。兒子作為一個5歲的男子漢,是要保護媽媽,照顧媽媽的。零食也要讓着媽媽。如果你實在不讓,我就自己搶了。於是周一早上我先兒子起床,把零食往包里塞呀,塞呀,全塞進去了。老公看不下去了,跟我說“你給兒子留兩個果凍”。好吧,得給老公面子,給兒子留了兩個小果凍,大的聖杯果凍就不要想了。但是看着零食就總想吃啊,這樣下面會變成胖紙。於是將所有的零食貢獻出來給同事們分了。一個不想長胖的吃貨是有多么的糾結~~ 國家領導人不管開什么會議,我大燕郊必堵車,周四早上8點出門,下午三點到的公司。於是周五堵車的路上自創了一首《一帶一路進京堵車千古版》,附上歌詞和視頻鏈接:http://www.le.com/ptv/vplay/29454385.html 夏晨冬夜,每日來回一遍 燈火闌珊,每天到家時間 左盼右盼,只待來年燕郊通地鐵 難斷言,還要多少個日月 夜不成眠,大佬在京相見 好壞參半,久違APIC藍 緊閉雙眼,身在車中心游千里遠 好艷羡,走路人更快過了檢查站 若一帶一路,通燕高速卻要堵 藍天美如碧樹,誰又為你在乎 若一帶一路,能對經濟發展有幫助 也不悔進京如此的堵 此文中內容均為我原創,如需轉載請注明出處:http://www.cnblogs.com/xiexj/p/6845029.html 謝謝哦~~ |
旁白:一般的面試都是從最簡單基本的問題開始。
面試官:請在黑板上寫出一個線程安全的單例模式的例子。
面試者:
其實線程安全的實現有很多種,根據業務場景可以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; } }
或者
public class Singleton{ private static Singletoninstance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } }
旁白:從這個例子上我能想到的知識點主要有三個
☆ 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並發編程實踐》這兩本書,大學學過的《數據結構與算法》《編譯原理》掌握的好也可以在面試中加分哦。