關於synchronized的面試題

在面試過程中,我們經常會被考察多線程的問題。多線程雖然好用,但若使用不當將會帶來很多併發問題。如何保證線程安全自然是繞不開的話題。

最近公司要招賢納士,在面試的過程中,我發現所有人都知道關鍵字synchronized是用來線程同步,保證線程安全的。但是再深入聊下去,很容易陷入尷尬之境。我也只能客氣地說“換個話題“,或者“今天就先聊到這裏“…(PS:會不會我太裝13了呢?但是簡歷上一般都是5+的工作經驗)

2020註定是不平凡的一年。正值疫情期間,每一次面試機會就顯得尤爲重要。所以特地抽出時間來寫這篇文章,希望能夠幫助每位面試者找到心儀的工作。

1. synchronized的簡介

synchronized要理解爲加鎖,而不是鎖,這個思維有助於你更好的理解線程同步。

1.1 synchronized的使用及各自的鎖對象

這裏簡要介紹一下,爲以後的內容做一下鋪墊:

  1. 普通方法 :鎖對象是this,所謂的方法鎖(本質上屬於對象鎖)
public synchronized void say(){
	 System.out.println("Hello,everyone...");
}
  1. 同步代碼塊(方法中):鎖對象是synchronized(obj)的對象,所謂的對象鎖
 public  void say(boolean isYou){
        synchronized (obj){
            System.out.println("Hello");
        }
    }
  1. 同步靜態方法:鎖對象是當前類的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的主要作用:

  1. 保持內存可見性;使所有線程都能看到共享內存的最新狀態。
  2. 防止指令重排的問題;

通過設置內存屏障實現的。感興趣的可以去看一下深入理解Java虛擬機

個人拙見,能答出來上面的內容即可,更深入的絕大部分都是在SHOW或者就是壓薪資…

2.2 基礎面試點

爲了節約各位看官的時間,先把結論給出來:

  1. 若是對象鎖,則每個對象都持有一把自己的獨一無二的鎖,且對象之間的鎖互不影響 。若是類鎖,所有該類的對象共用這把鎖。
  2. 一個線程獲取一把鎖,沒有得到鎖的線程只能排隊等待;
  3. synchronized 是可重入鎖,避免很多情況下的死鎖發生。
  4. synchronized 方法若發生異常,則JVM會自動釋放鎖。
  5. 鎖對象不能爲空,否則拋出NPE(NullPointerException)
  6. 同步本身是不具備繼承性的:即父類的synchronized 方法,子類重寫該方法,分情況討論:沒有synchonized修飾,則該子類方法不是線程同步的。(PS :涉及同步繼承性的問題要分情況)
  7. 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的方法,主要分爲兩種情況:

  1. 子類的方法沒有被synchronized修飾:

synchronized的不具備繼承性。所以子類方法是線程不安全的。

  1. 子類的方法被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做了優化也沒有提及。不要着急,後續補充…

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