在面試過程中,我們經常會被考察多線程的問題。多線程雖然好用,但若使用不當將會帶來很多併發問題。如何保證線程安全自然是繞不開的話題。
最近公司要招賢納士,在面試的過程中,我發現所有人都知道關鍵字synchronized是用來線程同步,保證線程安全的。但是再深入聊下去,很容易陷入尷尬之境。我也只能客氣地說“換個話題“,或者“今天就先聊到這裏“…(PS:會不會我太裝13了呢?但是簡歷上一般都是5+的工作經驗)
2020註定是不平凡的一年。正值疫情期間,每一次面試機會就顯得尤爲重要。所以特地抽出時間來寫這篇文章,希望能夠幫助每位面試者找到心儀的工作。
1. synchronized的簡介
synchronized要理解爲加鎖,而不是鎖,這個思維有助於你更好的理解線程同步。
1.1 synchronized的使用及各自的鎖對象
這裏簡要介紹一下,爲以後的內容做一下鋪墊:
- 普通方法 :鎖對象是this,所謂的方法鎖(本質上屬於對象鎖)
public synchronized void say(){
System.out.println("Hello,everyone...");
}
- 同步代碼塊(方法中):鎖對象是synchronized(obj)的對象,所謂的對象鎖
public void say(boolean isYou){
synchronized (obj){
System.out.println("Hello");
}
}
- 同步靜態方法:鎖對象是當前類的Class對象,即(XXX.class),所謂的類鎖
public static synchronized void work(){
System.out.println("Work hard...");
}
希望大家再遇到對象鎖,類鎖而不知所措…有時候遇到面試官把問題描述的不夠清楚時,要勇於及時和麪試官溝通。雖然找工作時總會遇到奇葩面試官,但是如果你遇到的機率太高時,請自覺地審視一下自己…
1.2 synchronized的關於代碼塊的疑問
看到上面synchronized的用法,你會有這樣的疑問嗎?synchronized能修飾類級別(靜態)代碼塊嗎?(PS: 這個話題是我臨時想起的,改天在面試中問一下看看效果…)
結論:synchronized不能用在類級別的(靜態)代碼塊
如果在面試中不給你編譯器,大多數人估計都是要mountain泰吧。這裏直接給出我的理解:
這個要從加載順序上考慮。
類級別的代碼塊在加載順序上是要優先於任何方法的,其執行順序只跟代碼位置先後有關。沒人跟你搶,自然不需要同步。
2. 涉及synchronized的面試題
這裏通過一個常見的面試題-單例模式來展開,這篇文章主要內容是考察synchronized關鍵字的,就直奔主題進入DCL(Double Check Lock)雙重校驗鎖的單例。
2.1 圍繞着DCL展開的話題
2.1.1 實現DCL
如果這步就過不了,就尷尬無止境啦…
public class SingleInstance {
private volatile static SingleInstance instance = null;
private SingleInstance(){ }
public static SingleInstance getInstance(){
if (instance == null){
synchronized (SingleInstance.class){
if (instance == null){
instance = new SingleInstance();
}
}
}
return instance;
}
}
2.1.2 談一下synchronized的作用
synchronized 關鍵字主要用來解決的是多線程同步問題,其可以保證在被其修飾的代碼任意時刻只有一個線程執行。視情況而定,(主動)說出它的用法及底層實現原理(使用的是moniterenter 和 moniterexit指令…),PS:synchronized的底層實現原理會單獨展開…
2.1.3 這裏(DCL)的volatile的作用
volatile只能保證變量的可見性,並不能保證對volatile修飾的變量的操作的原子性。
volatile的主要作用:
- 保持內存可見性;使所有線程都能看到共享內存的最新狀態。
- 防止指令重排的問題;
通過設置內存屏障實現的。感興趣的可以去看一下深入理解Java虛擬機
個人拙見,能答出來上面的內容即可,更深入的絕大部分都是在SHOW或者就是壓薪資…
2.2 基礎面試點
爲了節約各位看官的時間,先把結論給出來:
- 若是對象鎖,則每個對象都持有一把自己的獨一無二的鎖,且對象之間的鎖互不影響 。若是類鎖,所有該類的對象共用這把鎖。
- 一個線程獲取一把鎖,沒有得到鎖的線程只能排隊等待;
- synchronized 是可重入鎖,避免很多情況下的死鎖發生。
- synchronized 方法若發生異常,則JVM會自動釋放鎖。
- 鎖對象不能爲空,否則拋出NPE(NullPointerException)
- 同步本身是不具備繼承性的:即父類的synchronized 方法,子類重寫該方法,分情況討論:沒有synchonized修飾,則該子類方法不是線程同步的。(PS :涉及同步繼承性的問題要分情況)
- synchronized本身修飾的範圍越小越好。畢竟是同步阻塞。跑不快還佔着超車道…
2.2.1 同時訪問synchronized的靜態和非靜態方法,能保證線程安全嗎?
結論:不能,兩者的鎖對象不一樣。前者是類鎖(XXX.class),後者是this
2.2.2 同時訪問synchronized方法和非同步方法,能保證線程安全嗎?
結論:不能,因爲synchronized只會對被修飾的方法起作用。
2.2.3 兩個線程同時訪問兩個對象的非靜態同步方法能保證線程安全嗎?
結論:不能,每個對象都擁有一把鎖。兩個對象相當於有兩把鎖,導致鎖對象不一致。(PS:如果是類鎖,則所有對象共用一把鎖)
2.2.4 若synchronized方法拋出異常,會導致死鎖嗎?
JVM會自動釋放鎖,不會導致死鎖問題
2.2.5 若synchronized的鎖對象能爲空嗎?會出現什麼情況?
鎖對象不能爲空,否則拋出NPE(NullPointerException)
2.2.6 若synchronized的鎖對象能爲空嗎?會出現什麼情況?
鎖對象不能爲空,否則拋出NPE(NullPointerException)
2.3 關於繼承性的面試點
2.3.1 synchronized涉及的繼承性問題
重寫父類的synchronized的方法,主要分爲兩種情況:
- 子類的方法沒有被synchronized修飾:
synchronized的不具備繼承性。所以子類方法是線程不安全的。
- 子類的方法被synchronized修飾(這裏面試點主要考察鎖對象的歸屬問題):
兩個鎖對象其實是一把鎖,而且是子類對象作爲鎖。這也證明了: synchronized的鎖是可重入鎖。否則將出現死鎖問題。
2.4 實戰經驗的面試點
2.4.1 在開發過程中,你經常使用synchronized方法多還是synchronized代碼塊?and why?
關於synchronized 的內容部分,我在面試過程中經常問且只問這一道題。本人認爲這個能很好的考察面試者的綜合素質。(PS:畢竟是要擰螺絲的…)
synchronized同步的範圍是越小越好。因爲若該方法耗時很久,那其它線程必須等到該持鎖線程執行完才能運行。(黃花菜都涼了都…)
而synchronized代碼塊部分只有這一部分是同步的,其它的照樣可以異步執行,提高運行效率。
2.4.2 請寫一個死鎖的例子
這裏給大家一個參考:我有故事,你有酒嗎?
public class DeadLock {
String story = "故事";
String wine = "酒";
public void wantWine() throws InterruptedException {
synchronized (story){
System.out.println("已經擁有:"+ story +"就缺:"+ wine);
Thread.sleep(1000);
synchronized (wine){
System.out.println("擁有:"+ wine);
}
}
}
public void wantStory() throws InterruptedException {
synchronized (wine){
System.out.println("已經擁有:"+ wine +"就缺:"+ story);
Thread.sleep(1000);
synchronized (story){
System.out.println("擁有:"+ story);
}
}
}
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
deadLock.wantWine();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
deadLock.wantStory();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
2.4.2 有沒有遇到過synchronized失效的問題?
對於這種問法,主要是考察面試者在實際開發中的經驗是否豐富…,可以如實回答的。
synchronized 雖然用法簡單,但是如果鎖對象不一致,就會失效。排查問題的時候一定要着重從鎖對象是否一致上去判斷.
細心的讀者會發現,淨XC,居然沒有對synchronized的原理進行分析,以及在JDK1.6時,Java官方對synchronized做了優化也沒有提及。不要着急,後續補充…