BAT大廠面試必問專題之Java多線程
根據眾多面試的同學反饋的面試題,給大家整理一版最新的面試專題,希望對大家有所幫助。

Java多線程
1.先看問題
先來看看經常會問到的問題。
線程池的原理,為什么要創建線程池?創建線程池的方式;
線程的生命周期,什么時候會出現僵死進程;
說說線程安全問題,什么實現線程安全,如何實現線程安全;
創建線程池有哪幾個核心參數? 如何合理配置線程池的大小?
volatile、ThreadLocal的使用場景和原理;
ThreadLocal什么時候會出現OOM的情況?為什么?
synchronized、volatile區別、synchronized鎖粒度、模擬死鎖場景、原子性與可見性;
看看你能回答幾個哦。
然后我們再來看看每個問題我們應該怎么來回答吧!
2.再看答案
2.1 Q1:線程池的原理
線程池的原理,為什么要創建線程池?創建線程池的方式有哪些?
原理和創建線程池的實現請我之前整理的這篇文章:
Java線程池原理講解
創建線程池的幾種方式:
ThreadPoolExecutor
ThreadScheduledExecutor
ForkJoinPool
2.1 Q2:線程的生命周期
線程的生命周期,什么時候會出現僵死進程;
僵死進程是指子進程退出時,父進程并未對其發出的SIGCHLD信號進行適當處理,導致子 進程停留在僵死狀態等待其父進程為其收尸,這個狀態下的子進程就是僵死進程。
2.3 Q3:線程安全問題
說說線程安全問題,什么是線程安全,如何實現線程安全;
線程安全 - 如果線程執行過程中不會產生共享資源的沖突,則線程安全。
線程不安全 - 如果有多個線程同時在操作主內存中的變量,則線程不安全
實現線程安全的三種方式
1)互斥同步
臨界區:syncronized、ReentrantLock
信號量 semaphore
互斥量 mutex
2)非阻塞同步
CAS(Compare And Swap)
3)無同步方案
可重入代碼
使用Threadlocal 類來包裝共享變量,做到每個線程有自己的copy
線程本地存儲
多線程的安全機制:數據安全機制
2.4 Q4:線程池參數問題
創建線程池有哪幾個核心參數? 如何合理配置線程池的大小?
首先我們來看下核心參數:
public ThreadPoolExecutor( int corePoolSize, // 核心線程數量大小 int maximumPoolSize, // 線程池最大容納線程數 long keepAliveTime, // 線程空閑后的存活時長 TimeUnit unit, //緩存異步任務的隊列 //用來構造線程池里的worker線程 BlockingQueue
1
2
3
4
5
6
7
8
9
10
相關參數介紹:
當線程池中線程數量小于 corePoolSize 則創建線程,并處理請求。
當線程池中線程數量大于等于 corePoolSize 時,則把請求放入 workQueue 中,隨著線程池 中的核心線程們不斷執行任務,只要線程池中有空閑的核心線程,線程池就從 workQueue 中取 任務并處理。
當 workQueue 已存滿,放不下新任務時則新建非核心線程入池,并處理請求直到線程數目 達到 maximumPoolSize(最大線程數量設置值)。
如果線程池中線程數大于 maximumPoolSize 則使用 RejectedExecutionHandler 來進行任 務拒絕處理。
具體可以參考本文的詳解:線程池核心配置講解
然后我們需要來看先線程池的大小分配了。
線程池究竟設置多大要看你的線程池執行的什么任務了,CPU密集型、IO密集型、混合型,任 務類型不同,設置的方式也不一樣。
任務一般分為:CPU密集型、IO密集型、混合型,對于不同類型的任務需要分配不同大小的線程池。
3.1 CPU密集型
盡量使用較小的線程池,一般Cpu核心數+1
3.2 IO密集型
方法一:可以使用較大的線程池,一般CPU核心數 * 2 IO密集型CPU使用率不高,可以讓CPU等待IO的時候處理別的任務,充分利用cpu時間
方法二:(線程等待時間與線程CPU時間之比 + 1)* CPU數目
下面舉個例子:
比如平均每個線程CPU運行時間為0.5s,而線程等待時間(非CPU運行時間,比如IO)為1.5s,CPU核心數為8,那么根據上面這個公式估算得到:((0.5+1.5)/0.5)*8=32。這個公式進一步轉化為:
最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目
3.3 混合型
可以將任務分為CPU密集型和IO密集型,然后分別使用不同的線程池去處理,按情況而定
2.5 Q5:volatile和ThreadLocal
問題:volatile、ThreadLocal的使用場景和原理?
volatile變量進行寫操作時,JVM 會向處理器發送一條 Lock 前綴的指令,將這個變量所在緩 存行的數據寫會到系統內存。
Lock 前綴指令實際上相當于一個內存屏障(也成內存柵欄),它確保指令重排序時不會把其 后面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的后面;即在執行到內 存屏障這句指令時,在它前面的操作已經全部完成。
volatile的適用場景:
狀態標志,如:初始化或請求停機
一次性安全發布,如:單列模式
獨立觀察,如:定期更新某個值
“volatile bean” 模式
開銷較低的“讀-寫鎖”策略,如:計數器
ThreadLocal是用來維護本線程的變量的,并不能解決共享變量的并發問題。ThreadLocal是 各線程將值存入該線程的map中,以ThreadLocal自身作為key,需要用時獲得的是該線程之前 存入的值。如果存入的是共享變量,那取出的也是共享變量,并發問題還是存在的。
ThreadLocal的適用場景
場景:數據庫連接、Session管理
2.6 Q6:OOM情況
問題:ThreadLocal什么時候會出現OOM的情況?為什么?
ThreadLocal變量是維護在Thread內部的,這樣的話只要我們的線程不退出,對象的引用就會 一直存在。當線程退出時,Thread類會進行一些清理工作,其中就包含ThreadLocalMap, Thread調用exit方法如下:
&esmp;?ThreadLocal在沒有線程池使用的情況下,正常情況下不會存在內存泄露,但是如果使用了線程 池的話,就依賴于線程池的實現,如果線程池不銷毀線程的話,那么就會存在內存泄露。
2.7 Q7:synchronized、volatile區別
問題:synchronized、volatile有什么區別
volatile主要應用在多個線程對實例變量更改的場合,刷新主內存共享變量的值從而使得各個 線程可以獲得最新的值,線程讀取變量的值需要從主存中讀取;synchronized則是鎖定當前變 量,只有當前線程可以訪問該變量,其他線程被阻塞住。另外,synchronized還會創建一個內 存屏障,內存屏障指令保證了所有CPU操作結果都會直接刷到主存中(即釋放鎖前),從而保證 了操作的內存可見性,同時也使得先獲得這個鎖的線程的所有操作
volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。 volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞,比如多個線程爭搶 synchronized鎖對象時,會出現阻塞。
volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的 修改可見性和原子性,因為線程獲得鎖才能進入臨界區,從而保證臨界區中的所有語句全部得到 執行。
volatile標記的變量不會被編譯器優化,可以禁止進行指令重排;synchronized標記的變量 可以被編譯器優化。
Java 任務調度 多線程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。