珍藏的n道多執行緒併發面試題,給你秋招保駕護航

qwer1030274531發表於2020-08-25

  為什麼要用執行緒池?Java的執行緒池內部機制,引數作用,幾種工作阻塞佇列,執行緒池型別以及使用場景

  回答這些點:

  為什麼要用執行緒池?

  Java的執行緒池原理

  執行緒池核心引數

  幾種工作阻塞佇列

  執行緒池使用不當的問題

  執行緒池型別以及使用場景

  為什麼要用執行緒池?

  執行緒池:一個管理執行緒的池子。

  管理執行緒,避免增加建立執行緒和銷燬執行緒的資源損耗。

  提高響應速度。

  重複利用。

  Java的執行緒池執行原理

  

在這裡插入圖片描述

  為了形象描述執行緒池執行,打個比喻:

  核心執行緒比作公司正式員工

  非核心執行緒比作外包員工

  阻塞佇列比作需求池

  提交任務比作提需求

  

在這裡插入圖片描述

  執行緒池核心引數

  public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

  long keepAliveTime,

  TimeUnit unit,

  BlockingQueue workQueue,

  ThreadFactory threadFactory,

  RejectedExecutionHandler handler)

  123456

  corePoolSize: 執行緒池核心執行緒數最大值

  maximumPoolSize: 執行緒池最大執行緒數大小

  keepAliveTime: 執行緒池中非核心執行緒空閒的存活時間大小

  unit: 執行緒空閒存活時間單位

  workQueue: 存放任務的阻塞佇列

  threadFactory: 用於設定建立執行緒的工廠,可以給建立的執行緒設定有意義的名字,可方便排查問題。

  handler:線城池的飽和策略事件,主要有四種型別拒絕策略。

  四種拒絕策略

  AbortPolicy(丟擲一個異常,預設的)

  DiscardPolicy(直接丟棄任務)

  DiscardOldestPolicy(丟棄佇列裡最老的任務,將當前這個任務繼續提交給執行緒池)

  CallerRunsPolicy(交給執行緒池呼叫所在的執行緒進行處理)

  幾種工作阻塞佇列

  ArrayBlockingQueue(用陣列實現的有界阻塞佇列,按FIFO排序量)

  LinkedBlockingQueue(基於連結串列結構的阻塞佇列,按FIFO排序任務,容量可以選擇進行設定,不設定的話,將是一個無邊界的阻塞佇列 )

  DelayQueue(一個任務定時週期的延遲執行的佇列)

  PriorityBlockingQueue(具有優先順序的無界阻塞佇列)

  SynchronousQueue(一個不儲存元素的阻塞佇列,每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態)

  執行緒池使用不當的問題

  執行緒池適用不當可能導致記憶體飆升問題哦

  執行緒池型別以及使用場景

  newFixedThreadPool

  適用於處理CPU密集型的任務,確保CPU在長期被工作執行緒使用的情況下,儘可能的少的分配執行緒,即適用執行長期的任務。

  newCachedThreadPool

  用於併發執行大量短期的小任務。

  newSingleThreadExecutor

  適用於序列執行任務的場景,一個任務一個任務地執行。

  newScheduledThreadPool

  週期性執行任務的場景,需要限制執行緒數量的場景

  newWorkStealingPool

  建一個含有足夠多執行緒的執行緒池,來維持相應的並行級別,它會透過工作竊取的方式,使得多核的 CPU 不會閒置,總會有活著的執行緒讓 CPU 去執行,本質上就是一個 ForkJoinPool。)

  談談volatile關鍵字的理解

  volatile是面試官非常喜歡問的一個問題,可以回答以下這幾點:

  vlatile變數的作用

  現代計算機的記憶體模型(嗅探技術,MESI協議,匯流排)

  Java記憶體模型(JMM)

  什麼是可見性?

  指令重排序

  volatile的記憶體語義

  as-if-serial

  Happens-before

  volatile可以解決原子性嘛?為什麼?

  volatile底層原理,如何保證可見性和禁止指令重排(記憶體屏障)

  vlatile變數的作用?

  保證變數對所有執行緒可見性

  禁止指令重排

  現代計算機的記憶體模型

  

在這裡插入圖片描述

  其中快取記憶體包括L1,L2,L3快取~

  快取一致性協議,可以瞭解MESI協議

  匯流排(Bus)是計算機各種功能部件之間傳送資訊的公共通訊幹線,CPU和其他功能部件是透過匯流排通訊的。

  處理器使用嗅探技術保證它的內部快取、系統記憶體和其他處理器的快取資料在匯流排上保持一致。

  Java記憶體模型(JMM)

  

在這裡插入圖片描述

  什麼是可見性?

  可見性就是當一個執行緒 修改一個共享變數時,另外一個執行緒能讀到這個修改的值。

  指令重排序

  指令重排是指在程式執行過程中,為了提高效能, 編譯器和CPU可能會對指令進行重新排序。

  

在這裡插入圖片描述

  volatile的記憶體語義

  當寫一個 volatile 變數時,JMM 會把該執行緒對應的本地記憶體中的共享變數值重新整理到主記憶體。

  當讀一個 volatile 變數時,JMM 會把該執行緒對應的本地記憶體置為無效。執行緒接下來將從主記憶體中讀取共享變數。

  as-if-serial

  如果在本執行緒內觀察,所有的操作都是有序的;即不管怎麼重排序(編譯器和處理器為了提高並行度),(單執行緒)程式的執行結果不會被改變。

  double pi = 3.14; //A

  double r = 1.0; //B

  double area = pi * r * r; //C

  123

  步驟C依賴於步驟A和B,因為指令重排的存在,程式執行順訊可能是A->B->C,也可能是B->A->C,但是C不能在A或者B前面執行,這將違反as-if-serial語義。

  

在這裡插入圖片描述

  Happens-before

  Java語言中,有一個先行發生原則(happens-before):

  程式次序規則:在一個執行緒內,按照控制流順序,書寫在前面的操作先行發生於書寫在後面的操作。

  管程鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作

  volatile變數規則:對一個變數的寫操作先行發生於後面對這個變數的讀操作

  執行緒啟動規則:Thread物件的start()方法先行發生於此執行緒的每個一個動作

  執行緒終止規則:執行緒中所有的操作都先行發生於執行緒的終止檢測,我們可以透過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到執行緒已經終止執行

  執行緒中斷規則:對執行緒interrupt()方法的呼叫先行發生於被中斷執行緒的程式碼檢測到中斷事件的發生

  物件終結規則:一個物件的初始化完成先行發生於他的finalize()方法的開始

  傳遞性:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C

  volatile可以解決原子性嘛?為什麼?

  不可以,可以直接舉i++那個例子,原子性需要synchronzied或者lock保證

  public class Test {

  public volatile int race = 0;

  public void increase() {

  race++;

  }

  public static void main(String[] args) {

  final Test test = new Test();

  for(int i=0;i<10;i++){

  new Thread(){

  public void run() {

  for(int j=0;j<100;j++)

  test.increase();

  };

  }.start();

  }

  //等待所有累加執行緒結束

  while(Thread.activeCount()>1)

  Thread.yield();

  System.out.println(test.race);

  }

  }

  123456789101112131415161718192021222324

  volatile底層原理,如何保證可見性和禁止指令重排(記憶體屏障)

  volatile 修飾的變數,轉成彙編程式碼,會發現多出一個lock字首指令。lock指令相當於一個記憶體屏障,它保證以下這幾點:

  重排序時不能把後面的指令重排序到記憶體屏障之前的位置

  將本處理器的快取寫入記憶體

  如果是寫入動作,會導致其他處理器中對應的快取無效。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2714537/,如需轉載,請註明出處,否則將追究法律責任。

相關文章