Java常見面試題(一)多線程相關

1、一個線程兩次調用 start() 方法會出現什麼情況?談談線程的生命週期。
Java 的線程是不允許啓動兩次的,第二次調用必然會拋出 IllegalThreadStateException,這是一種運行時異常,多次調用 start 被認爲是編程錯誤。

在第二次調用 start() 方法的時候,線程可能處於終止或者其他(非 NEW)狀態,但是不論如何,都是不可以再次啓動的。

線程生命週期

  • 新建(NEW),表示線程被創建出來還沒真正啓動的狀態,可以認爲它是個 Java 內部狀態。
  • 就緒(RUNNABLE),表示該線程已經在 JVM 中執行,當然由於執行需要計算資源,它可能是正在運行,也可能還在等待系統分配給它 CPU 片段,在就緒隊列裏面排隊。
  • 阻塞(BLOCKED),這個狀態和我們前面兩講介紹的同步非常相關,阻塞表示線程在等待 Monitor lock。比如,線程試圖通過 synchronized 去獲取某個鎖,但是其他線程已經獨佔了,那麼當前線程就會處於阻塞狀態。
  • 等待(WAITING),表示正在等待其他線程採取某些操作。一個常見的場景是類似生產者消費者模式,發現任務條件尚未滿足,就讓當前消費者線程等待(wait),另外的生產者線程去準備任務數據,然後通過類似 notify 等動作,通知消費線程可以繼續工作了。Thread.join() 也會令線程進入等待狀態。
  • 計時等待(TIMED_WAIT),其進入條件和等待狀態類似,但是調用的是存在超時條件的方法,比如 wait 或 join 等方法。
  • 終止(TERMINATED),不管是意外退出還是正常執行結束,線程已經完成使命,終止運行,也有人把這個狀態叫作死亡。
    在這裏插入圖片描述

2、Java程序什麼情況下會產生死鎖?。
死鎖是一種特定的程序狀態,在實體之間,由於循環依賴導致彼此一直處於等待之中,沒有任何個體可以繼續前進。死鎖不僅會發生在線程之間會,存在資源獨佔的進程之間同樣也可能出現死鎖。在這裏插入圖片描述
定位死鎖常用工具:

  • jstack
  • jconsole

*如何在編程中儘量預防死鎖

  • 儘量避免使用多個鎖,並且只有需要時才持有鎖。
  • 如果必須使用多個鎖,儘量設計好鎖的獲取順序。
  • 使用帶超時的方法,類似 Object.wait(…) 或者 CountDownLatch.await(…),都支持 timed_wait,我們完全可以就不假定該鎖一定會獲得,指定超時時間,併爲無法得到鎖時準備退出邏輯。

死鎖代碼示例

public class DeadLockSample extends Thread{
    private String first;
    private String second;
    public DeadLockSample(String name, String first, String second) {
        super(name);
        this.first = first;
        this.second = second;
    }

    public  void run() {
        synchronized (first) {
            System.out.println(this.getName() + " obtained: " + first);
            try {
                Thread.sleep(1000L);
                synchronized (second) {
                    System.out.println(this.getName() + " obtained: " + second);
                }
            } catch (InterruptedException e) {
                /// TODO
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        String lockA = "lockA";
        String lockB = "lockB";
        DeadLockSample t1 = new DeadLockSample("Thread1", lockA, lockB);
        DeadLockSample t2 = new DeadLockSample("Thread2", lockB, lockA);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

3、wait和sleep方法的區別。

  • 屬於不同的兩個類,sleep()方法是線程類(Thread)的靜態方法,wait()方法是Object類裏的方法。
  • sleep()方法不會釋放鎖,wait()方法釋放對象鎖,一旦一個對象調用了wait方法,必須要採用notify()和notifyAll()方法喚醒該進程。
  • sleep()方法可以在任何地方使用,wait()方法則只能在同步方法或同步塊中使用。
  • sleep()必須捕獲異常,wait()方法、notify()方法和notiftAll()方法不需要捕獲異常。
  • sleep()使線程進入阻塞狀態(線程睡眠),wait()方法使線程進入等待隊列(線程掛起),也就是阻塞類別不同。

4、synchronized與Lock的區別

  • 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java。
  • synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖。
  • synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖。
  • .用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了。
  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
  • Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。

參考:詳解synchronized與Lock的區別與使用

5、談一下線程池類ThreadPoolExecutor各個參數以及參數之間的關係?

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

  • corePoolSize:線程池中核心線程數的最大值
  • maximumPoolSize:線程池中能擁有最多線程數
  • workQueue:用於緩存任務的阻塞隊列

我們現在通過向線程池添加新的任務來說明着三者之間的關係。

​ (1)如果沒有空閒的線程執行該任務且當前運行的線程數少於corePoolSize,則添加新的線程執行該任務。

​ (2)如果沒有空閒的線程執行該任務且當前的線程數等於corePoolSize同時阻塞隊列未滿,則將任務入隊列,而不添加新的線程。

​ (3)如果沒有空閒的線程執行該任務且阻塞隊列已滿同時池中的線程數小於maximumPoolSize,則創建新的線程執行任務。

​ (4)如果沒有空閒的線程執行該任務且阻塞隊列已滿同時池中的線程數等於maximumPoolSize,則根據構造函數中的handler指定的策略來拒絕新的任務。

keepAliveTime:表示空閒線程的存活時間。

  • keepAliveTime:表示空閒線程的存活時間。
  • 表示keepAliveTime的單位。
  • handler:表示當workQueue已滿,且池中的線程數達到maximumPoolSize時,線程池拒絕添加新任務時採取的策略。
  • threadFactory:指定創建線程的工廠

參考:https://blog.csdn.net/jubaoquan/article/details/79198780 http://www.crazyant.net/2124.html

[ThreadPoolExecutor策略配置以及應用場景](https://segmentfault.com/a/1190000008394155)

6、Executors 提供的線程池有哪幾種? 分別有什麼特點?

  • newCachedThreadPool(),它是一種用來處理大量短時間工作任務的線程池,具有幾個鮮明特點:它會試圖緩存線程並重用,當無緩存線程可用時,就會創建新的工作線程;如果線程閒置的時間超過 60 秒,則被終止並移出緩存;長時間閒置時,這種線程池,不會消耗什麼資源。其內部使用 SynchronousQueue 作爲工作隊列。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • newFixedThreadPool(int nThreads),重用指定數目(nThreads)的線程,其背後使用的是無界的工作隊列,任何時候最多有 nThreads 個工作線程是活動的。這意味着,如果任務數量超過了活動隊列數目,將在工作隊列中等待空閒線程出現;如果有工作線程退出,將會有新的工作線程被創建,以補足指定的數目 nThreads。
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • newSingleThreadExecutor(),它的特點在於工作線程數目被限制爲 1,操作一個無界的工作隊列,所以它保證了所有任務的都是被順序執行,最多會有一個任務處於活動狀態,並且不允許使用者改動線程池實例,因此可以避免其改變線程數目。
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • newSingleThreadScheduledExecutor()newScheduledThreadPool(int corePoolSize),創建的是個 ScheduledExecutorService,可以進行定時或週期性的工作調度, newSingleThreadScheduledExecutor() 是單一工作線程, newScheduledThreadPool(int corePoolSize)是多個工作線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章