面試必備:多線程學習(一)

這是2020年“水”的第23篇文章

面試中,多線程併發問題基本上是必問的,所以,不背上個線程相關的問題,都不好意思出去面試了。

一提到多線程,相信大部分小夥伴首先想到的一定是 SynchronizeLock,再就是volatileconcurrent併發包,厲害點的小夥伴呢,還能再跟面試官吹吹Synchronizevolatile的區別跟原理,以及併發包常用的數據結構,例如之前提到的 ConcurrentHashMap

好了,廢話不多說,本篇就以面試爲出發點,看看多線程到底要準備哪些傻吊知識點。

1、多線程的創建方式

  1. 繼承Thread
  2. 實現Runnable接口
  3. 實現Callable接口

一共3種創建方式,不要問哪個好,問就是接口形式的好,因爲實現接口的方式比繼承類的方式更靈活,也能減少程序之間的耦合度,面向接口編程也是設計模式6大原則的核心,但是,是選擇實現Runnable,還是Callable,就要根據自己情況選擇了。

延伸補充:Runnable跟Callable區別?

如果上邊的問題回答了Runnable、Callable,那麼就要準備好關於兩者的區別?什麼?沒用過Callable?背就完事了。

Runnable沒有返回值,需要做的事情就是去執行run()方法,執行完就完了。
Callable有返回值,該返回值是Callable接口中的call()方法,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。

簡單看一下代碼:

Callable callable = new Callable() {
  @Override
  public String call() throws Exception 
{
      Thread.sleep(5000);
      return "我是call方法返回值";
  }
};
Future future = executor.submit(callable);
System.out.println("獲取返回值: "+future.get());

看完上方代碼,如果問到Callable接口會有哪些應用場景應該會答了吧?

2、wait() 和 notify() 的理解和使用?

有時候面試官會先延伸一下它的出處,比如,你能說一下Object類都有哪些方法嗎?

只要你提到了 wait() 跟 notify() || notifyAll() 那麼基本就會問相關的內容了。

wait和notify是用來讓線程進入等待狀態以及使線程喚醒的兩個操作。

理解:

wait、notify 屬於 Object 基礎類,所以每個對象都有 wait、notify 的功能,也就是每個對象都有鎖的權利;但是在使用此類方法的時候,一定要對競爭資源進行加鎖操作,獲得這個鎖對象的控制權,比如放在 Synchronize(obj) 代碼塊中,如果不加鎖的話,則會報 IllegalMonitorStateException 異常。

使用:

public class Test {

    public static void main(String[] args{
        Object obj = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (obj) {
                try {
                    System.out.println("1-before wait");
                    obj.wait();
                    System.out.println("1-after wait");
                } catch (Exception e) {
                    System.out.println(String.format("1-catch :%s. interrupt狀態: %s",e.toString(),Thread.currentThread().isInterrupted()));
                    Thread.currentThread().interrupt();
                    System.out.println("設置標誌位後interrupt狀態: "+ Thread.currentThread().isInterrupted());

                }
            }
        });

        thread1.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("線程初始interrupt狀態:"+thread1.isInterrupted());
        thread1.interrupt();

    }
}

如果你不太清楚上方代碼中的 interrupt()、interrupted() 和isInterrupted() 那麼可以參考這篇文章:Thread類中interrupt方法詳解

廢話總結:

wait和notify來自於Object基礎類;
wait和notify是用來讓線程進入等待狀態以及使線程喚醒的兩個操作。
wait或notify方法,使用時必須保證當前運行的線程取得了該對象的控制權。
如果在沒有控制權的線程裏執行wait或notify方法,會報IllegalMonitorStateException異常。

3、sleep() 和 wailt() 的區別

如果在上一個wailt、notify中沒有回答出wailt的細節的話,那麼這個問題就會常問;

首先兩者來源不同,sleep來自Thread類,而wait來自Object類。

sleep方法和wait方法都可以用來放棄CPU一定的時間,不同點在於如果線程持有某個對象的監視器,sleep方法不會放棄這個對象的監視器,而wait方法會放棄這個對象的監視器,怎麼講?

sleep方法不會釋放鎖,調用sleep後不會出讓系統資源,典型無賴行爲「佔着茅坑不拉*」;而wait則是進入線程等待池等待,會出讓系統資源,其他線程可以佔用CPU,就是我現在不用了,你們先用吧。

通常wait是不會加時間限制的,因爲如果wait線程的運行資源不夠,即使出來也沒用,要等待其他線程調用notify/notifyAll喚醒等待池中的所有線程,纔會進入就緒隊列等待系統分配資源。

而sleep(秒)可以用指定時間使它自動喚醒過來,如果時間不到只能調用interrupt()強行打斷。比如上端代碼中我用到了 thread1.interrupt(); 方法,作用就是強行打斷。

使用範圍不同:wait只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用。所以你看到的只要出現wait必出現Synchronize,而sleep可以到處使用。

廢話總結:

所屬類不同:sleep() 來自Thread;wailt() 來自 Object
是否釋放鎖:sleep() 不會釋放鎖;wailt() 會釋放鎖
用法不同:sleet時間到了會自動恢復;wailt() 可以使用notigy()/notigyAll()直接喚醒
使用範圍不同:sleep() 在任何地方可用;wait() 需要結合 Synchronize使用
作用不同:sleep() 通常被用於暫停執行;wait() 通常被用於線程間交互/通信

4、如何保證多線程安全?

  1. 使用安全類,比如 java.util.concurrent 併發包下的類;
  2. 使用鎖,比如自動Synchronize,手動鎖 Lock

如上兩點只是儘量做到線程安全,或者說目前被建議以及常用的,但做到絕對的線程安全通常需要付出許多額外代價,Java中標註自己是線程安全的類,實際上絕大多數都不是線程安全的,不過絕對線程安全的類,Java中也有,比方說CopyOnWriteArrayList、CopyOnWriteArraySet

廢話總結:

如果你的代碼,在多線程下執行和在單線程下執行,永遠都能得到一樣的結果,那麼你的代碼就是線程安全的。

5、Synchronize跟Lock區別

提到Synchronize就不得不扯上Lock,冤家路窄,面試總是會問。

Synchronize:java中提供的一個併發控制的關鍵字,自動鎖、重量級。主要用在同步方法和同步代碼塊中,也就是說,synchronized既可以修飾方法也可以修飾代碼塊。被synchronized修飾的代碼塊及方法,在同一時間,只能被單個線程訪問。應用場景:商品下單,商品庫存的修改。

Lock:是一個類,確切說是一個接口類,其實現類可以實現同步訪問。Lock爲輕量級、手動鎖,能夠實現synchronized所實現的功能,但Lock相對更加靈活,可以被繼承、可以有方法、可以有各種各樣的類變量。

轉自網上的區別表格:

類別synchronizedLock
存在層次 Java的關鍵字,在jvm層面上 是一個類
鎖的釋放 1、以獲取鎖的線程執行完同步代碼,釋放鎖 2、線程執行發生異常,jvm會讓線程釋放鎖 在finally中必須釋放鎖,不然容易造成線程死鎖
鎖的獲取 假設A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會一直等待 分情況而定,Lock有多個鎖獲取的方式,具體下面會說道,大致就是可以嘗試獲得鎖,線程可以不用一直等待
鎖狀態 無法判斷 可以判斷
鎖類型 可重入 不可中斷 非公平 可重入 可判斷 可公平(兩者皆可)
性能 少量同步 大量同步

個人補充:

如果單單寫兩者,完全可以寫一篇文章,由於本篇是出於面試準備的,所以面試官問到時,簡單說一下兩者的作用,以及各自的應用場景即可『經驗初級』,當然,如果你有特地看過源碼的話,自然是建議你引導面試官問你原理,你懂得~ 不過話說回來,synchronized 原理還是有必要了解的~

廢話總結:

1、Synchronized爲java關鍵字,由JVM控制;Lock爲接口類,由JDK實現;
2、Synchronized重量級、自動鎖,Lock輕量級、手動鎖;
3、發生異常時,Synchronized會自動釋放鎖,Lock需要手動在finally釋放鎖,不釋放很容易造成死鎖;
4、Lock更加靈活,能夠響應中斷,讓等待狀態的線程停止等待,synchronized不行;
5、通過Lock可以知道線程是否成功獲得了鎖,synchronized不行;

最後

本篇爲多線程面試準備的第一篇,標籤爲左側分欄的:面試相關

目前只准備了5個小問題,會結合自己近期的準備持續更新下一篇。

博客地址:https://www.cgblog.com/niceyoo

如果覺得這篇文章有丶東西,不防關注一下我,關注是對我最大的鼓勵~

18年專科畢業後,期間一度迷茫,最近我創建了一個公衆號用來記錄自己的成長。

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