Java線程面試題


進程與線程的區別

  • 進程是資源分配的最小單位
  • 線程是CPU調度的最小單位
  • 一個程序必須有一個進程,一個進程必須有一個線程

Thread中start和run方法的區別

  • 調用start()方法會創建一個新的子線程並啟動
  • run()方法只是Thread的一個普通方法的調用

Thread和Runnable的關系

  • Thread是實現了Runnable接口的類,使得run支持多線程
  • 因類的單一繼承原則,推薦多使用Runnable接口

線程的狀態

  • 新建(New):創建后尚位啟動的線程的狀態
  • 運行(Runnable):包含Running和Ready
  • 無限期等待(Waiting):不會被分配CPU執行時間,需要顯式唄喚醒
  • 限期等待(Timed Waiting):在一定時間后會由系統自動喚醒
  • 阻塞(Blocked):等待排它鎖
  • 結束(Terminated):已終止線程的狀態,線程已經結束執行

sleep和wait的區別

  • sleep是Thread類的方法,wait是Object類中定義的方法
  • sleep()方法可以在任何地方使用
  • wait()方法只能在sychronized方法或synchronized塊中使用
    • Thread.sleep只會讓出CPU,不會導致鎖行為的改變
    • Object.wait不僅讓出CPU,還會釋放已經占有的同步資源鎖

鎖池與等待池

notify和notifyAll的區別

  • notifyAll:會讓所有處於等待池的線程全部進入鎖池去競爭獲取鎖的機會
  • notify:會隨機讓一個處於等待池中的線程進入鎖池去競爭獲取鎖的機會

interrupt函數

通知線程應該中斷了

  • 如果線程處於阻塞狀態,那么線程將立即退出被阻塞狀態,拋出一個InterruptedException異常
  • 如果線程處於正常活動狀態,那么會將該線程的中斷標志設置為true。被設置中斷標志的線程將繼續正常運行,不受影響

線程狀態以及狀態之間的轉換

synchronized底層實現原理

  • Java對象頭

    對象在內存中的布局

    ​ 對象頭

    ​ 實例數據

    ​ 對齊填充

  • Monitor

Monitor:每個Java對象天生自帶了一把看不見的鎖

Monitor存在於每個Java對象的對象頭中

synchronized的四種狀態

無鎖、偏向鎖、輕量級鎖、重量級鎖

  • 無鎖:沒有添加任何的鎖,此時的目標共享數據沒用被任何一個線程占用

  • 偏向鎖:如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word的結構也變為偏向鎖結構,當該線程再次請求鎖時,無需再做任何同步操作(CAS),即獲得鎖的過程只需要檢測Mark Word的鎖標記位是否為偏向鎖以及當前線程ID是否等於Mark Word的TreadID即可,這樣就省去了大量有關鎖申請的操作,適用范圍:目標共享數據只被同一個線程占用的時候

  • 輕量級鎖:輕量級鎖是由偏向鎖升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖,適用范圍:目標共享數據被兩個線程交替占用的時候

    • 輕量級鎖加鎖過程(參考的博客

      1. 在線程執行同步代碼塊之前,JVM會現在當前線程的棧楨中創建用於存儲鎖記錄的空間,並將鎖對象頭中的 markWord 信息復制到鎖記錄中,這個官方稱為 Displaced Mard Word。然后線程嘗試使用 CAS 將對象頭中的 MarkWord 替換為指向鎖記錄的指針。

      2. 將鎖對象頭中的 markWord 信息復制到鎖記錄中,這個官方稱為 Displaced Mard Word。然后線程嘗試使用 CAS 將對象頭中的 MarkWord 替換為指向鎖記錄的指針。如果替換成功,則進入步驟3(上鎖成功),失敗則進入步驟4(上鎖失敗)。

      3. CAS 替換成功說明當前線程已獲得該鎖,此時在棧楨中鎖標志位信息也更新為輕量級鎖狀態:00。此時的棧楨與鎖對象頭的狀態如圖所示。

      4. 如果CAS 替換失敗則說明當前時間鎖對象已被某個線程占有,那么此時當前線程只有通過自旋的方式去獲取鎖。如果在自旋一定次數后仍未獲得鎖,那么輕量級鎖將會升級成重量級鎖。

        升級成重量級鎖帶來的變化就是對象頭中鎖標志位將變為 10(重量級鎖),MarkWord 中存儲的也就是指向互斥量(重量級鎖)的指針。(注意!!!此時,鎖對象頭的 MarkWord 已經發生了改變!會導致正在占有鎖的線程解鎖時,將鎖記錄CAS回對象頭中失敗,從而會釋放重量級鎖並喚醒阻塞的線程了)。

        輕量級鎖升級過程流程圖:

    • 輕量級鎖解鎖過程

      • 解鎖成功:輕量級鎖解鎖時,會使用CAS將之前復制在棧楨中的 Displaced Mard Word 替換回 Mark Word 中。如果替換成功,則說明整個過程都成功執行,期間沒有其他線程訪問同步代碼塊。(此時仍為輕量級鎖)

      • 解鎖失敗:但如果替換失敗了,表示當前線程在執行同步代碼塊期間,有其他線程也在訪問,當前鎖資源是存在競爭的,那么鎖將會膨脹成重量級鎖。上圖線程一重量級鎖部分也就驗證了鎖膨脹的過程(此時膨脹為重量級鎖)。

Java線程池

通過new ThreadPoolExecutor(設置參數)或new ScheduledThreadPoolExecutor(設置參數)或new ForkJoinPool(設置參數) 來實現下列對應功能的線程池

ThreadPoolExecutor的構造函數

  • corePoolSize:核心線程數量(初始的線程數量)

  • maximumPoolSize:線程不夠用時能夠創建的最大線程數量

  • workQueue:任務等待隊列,當任務數大於maximumPoolSize時,多出來暫時沒線程處理的任務,放進任務等待隊列中

  • keepAliveTime:除核心線程外的線程,當沒有新的任務交給除核心線程外的線程處理,該線程會在keepAliveTime時間過后,自動銷毀線程

  • threadFactory:創建新線程的方式,默認是(Executors.defaultThreadFactory())

  • handler:線程池的飽和策略,如果堵塞隊列(workQueue)滿了,並且沒用多余的線程可以執行任務,此時執行的策略方式

    • AbortPolicy:直接拋出異常,這是默認策略
    • CallerRunsPolicy:用調用者所在的線程來執行任務
    • DiscardOldestPolicy:丟棄隊列中靠最前的任務,並執行當前任務
    • DiscardPolicy:直接丟棄當前任務
    • 通過實現RejectedExecutionHandler接口,來自定義handler策略

線程池的狀態

  • running:能接受新提交的任務,並且也能處理堵塞隊列中的任務

  • shutdonw:不再接受新提交的任務,但可以處理堵塞隊列的任務

  • stop:不再接受新提交的任務,也不處理堵塞隊列的任務

  • tidying:所有的任務都已終止

  • terminated:線程池已終止


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM