CS-LogN思惟導圖:記錄專業基礎 面試題
開源地址:https://github.com/FISHers6/CS-LogNjava

多線程與併發基礎
實現多線程
面試題1:有幾種實現線程的方法,分別是什麼
-
1.繼承Thread類,啓動線程的惟一方法就是經過 Thread 類的 start()實例方法,start()方法是一個 native 方法,它將啓動一個新線程去執行 run()方法git
-
2.實現 Runnable 接口,重寫run()函數,做爲參數放到Thread類構造函數中做爲target屬性,運行start()方法github
-
線程池建立線程、Callable本質仍是使Runnable建立,Callable是父輩類繼承了Runnable,線程池需傳入參數面試
面試題2:實現Runnable方法好,仍是繼承Thread類好
-
實現Runnable接口更好算法
- 1.單一繼承原則,若是繼承了Thread,就不能繼承其它類了,限制了可擴展性
- 2.Thread類每次只能建立一個獨立的線程,損耗大,而Runnable能利用線程池工具來建立線程
- 3.從代碼架構上看,run內容應該與Trhead代碼解耦
面試題3:一個線程兩次調用start方法會出現什麼狀況(考察源碼)
- 第二次會出現異常,從start源碼上和線程生命週期上分析,一個線程start後,
改變了threadState狀態字;而第二次再start每次會先檢查這個狀態不是0就報異常
面試題4:既然start方法會調用run方法,爲何咱們仍是要用start方法,而不是直接調用run方法呢(考察源碼)
- 由於start後線程纔會通過完整的線程生命週期,start調用native start0,虛擬機執startThread,thread_entry入口中調用Thread的run,
面試題5:start和run有什麼區別
- run()方法:只是普通的方法,調用run普通方法,能夠重複屢次調用
- start()方法,會啓動一個線程,使得虛擬機去調用Runnable對象的run()方法,不能屢次啓動同一個線程
面試題6:start方法如何調用run方法的(考察源碼和JVM)
- start方法調用native start0,JVM虛擬機執行startThread,在thread_entry中調用Thread的run方法
面試題7:如何正確中止線程
- 使用interrupt中斷通知,而不是強制,中斷通知後會讓被中止線程去決定什麼時候中止,即把主動權交給須要被中斷的線程
線程的生命週期
面試題1:Java線程有哪幾種狀態 說說生命週期
Thread和Object類中
與線程相關的重要方法緩存
面試題1:實現兩個線程交替打印奇數偶數
面試題2:手寫生產者消費者設計模式,爲何用該模式
面試題3:wait後發生了什麼,爲何須要在同步代碼內才能使用
- 從jvm的源碼實現上看,wait後,線程讓出佔有的cpu並釋放同步資源鎖;把本身加入到等待池,之後不會再主動參與cpu的競爭,除非被其它notify命中
- 爲了確保線程安全;另外wait會釋放資源,因此確定要先拿到這個鎖,能進入同步代碼塊已經拿到了鎖
面試題4:爲何線程通訊的方法wait,notify和notifyAll放在Object類,而sleep定義在Thread類裏 (考察對象鎖)
- 與對象的鎖有關,對象鎖綁定在對象的對象頭中,且放在Object裏,使每一個線程均可以持有多個對象的鎖
面試題5:wait方法是屬於Object對象的,那調用Thread.wait會怎麼樣
- 線程死的時候會本身notifyAll,釋放掉全部的持有本身對象的鎖。這個機制是實現不少同步方法的基礎。若是調用Thrad.wait,干擾了咱們設計的同步業務流程
面試題6:如何選擇notify仍是notifyAll
- 優先選用notifyAll,喚醒全部線程;除非業務須要每次只喚醒一個線程的
面試題7:notfiy後發生的操做,notifyAll以後全部的線程都會再次搶奪鎖,若是某線程搶奪失敗怎麼辦?
- notify後,讓waiterSet等待池中的一個線程與entry_List鎖池一級活躍線程一塊兒競爭CPU
- 搶奪鎖失敗後會繼續待在原鎖池或原等待池,等待競爭CPU的調度
面試題8:sleep方法與notify/wait方法的異同點
- 相同點:線程都會進入waiting狀態,均可以響應中斷
- 不一樣點:1.所屬類不一樣;2.wait/notify必須用在同步方法中,且會釋放鎖;3.sleep能夠指定時間
面試題9:join方法後父線程進入什麼狀態
- waiting狀態,join內部調用wait,子線程結束後自動調用notifyAll喚醒(jvm:exit函數)
線程安全與性能
面試題1:守護線程和普通線程的區別
- 守護線程是服務於普通線程的,而且不會影響到jvm的退出
面試題2:什麼是線程安全
- 無論業務中遇到怎樣的多個線程訪問某對象或某方法的狀況,而在編程這個業務邏輯的時候,都不須要再額外作任何額外的處理(也就是能夠像單線程編程同樣),程序也能夠正常運行(不會由於多線程而出錯),就能夠稱爲線程安全
面試題3:有哪些線程不安全的狀況,什麼緣由致使的
- 1.數據爭用、同時操做,如數據讀寫因爲同時寫,非原子性操做致使運行結果錯誤,a++
- 2.存在競爭,順序不當,如死鎖、活鎖、飢餓
面試題4:什麼是多線程的上下文切換,及致使的後果
- 進程線程切換要保存所須要的CPU運行環境,如寄存器、棧、全局變量等資源
- 在頻繁的io以及搶鎖的時候,會致使密集的上下文切換,多線程切換時,因爲緩存和上下文的切換會帶來性能問題
面試題5:多線程致使的開銷有哪些
Java內存模型
面試題1:Java的代碼如何一步步轉化,最終被CPU執行的
-
- 最開始,咱們編寫的Java代碼,是*.java文件
- 在編譯(javac命令)後,從剛纔的.java文件會變出一個新的Java字節碼文件.class
- JVM會執行剛纔生成的字節碼文件(*.class),並把字節碼文件轉化爲機器指令
- 機器指令能夠直接在CPU上執運行,也就是最終的程序執行
- JVM實現會帶來不一樣的「翻譯」,不一樣的CPU平臺的機器指令又千差萬別,沒法保證併發安全的效果一致
面試題2:單例模式的做用和適用場景
- 單例模式:只獲取一次資源,全程序通用,節省內存和計算;保證多線程計算結果正確;方便管理;
好比日期工具類只須要一個實例就能夠,無需多個示例
面試題3:單例模式的寫法,考察(重排序、單例和高併發的關係)
-
餓漢式(靜態常量、靜態代碼塊)
- 原理1:static靜態常量在類加載的時候就初始化完成了,且由jvm保證線程安全,保證了變量惟一
- 原理2:靜態代碼塊中實例化和靜態常量相似;放在靜態代碼塊裏初始化,類加載時完成;
- 特徵:簡單,但沒有懶加載(須要時再加載)
-
懶漢式(加synchronized鎖)
- 對初始化的方法加synchronized鎖達到線程安全的目的,但效率低,多線程下變成了同步
- 懶漢式取名:用到的時候纔去加載
-
雙重檢查
-
代碼實現
- 屬性加volatile,兩次if判斷NULL值,第二次前加類鎖
-
優勢
-
爲何用雙重而不用單層
-
靜態內部類
- 須要理解靜態內部類的優勢,懶漢式加載,jvm加載順序
-
枚舉
-
代碼實現簡單
- public enum Singleton{
INSTANCE;
public void method(){}
}
-
保證了線程安全
- 枚舉是一個特殊的類,通過反編譯查看,枚舉最終被編譯成一個final的類,繼承了枚舉父類。各個實例經過static定義,本質就是一個靜態的對象,全部第一次使用的時候採起加載(懶加載)
-
避免反序列化破壞單例
- 避免了:好比用反射就繞過了構造方法,反序列化出多個實例
面試題4:單例模式各類寫法分別適用的場合
- 1.最好的方法是枚舉,因枚舉被編譯成final類,用static定義靜態對象,懶加載。既保證了線程安全又避免了反序列化破壞單例
- 2.若是程序一開始要加載的資源太多,考慮到啓動速度,就應該使用懶加載
- 3.若是是對象的建立須要配置文件(一開始要加載其它資源),就不適合用餓漢式
面試題5:餓漢式單例的缺點
面試題6:懶漢式單例的缺點
面試題7:單例模式的雙重檢查寫法爲何要用double-check
面試題8:爲何雙重檢查要用volatile
-
1.保證instance的可見性
- 類初始化分紅3條指令,重排序帶來NPE空虛指針問題,加volatile防止重排序
-
2.防止初始化指令重排序
面試題9:講一講什麼是Java的內存模型
- 1.是一組規範,須要JVM實現遵照這個規範,以便實現安全的多線程程序
2.volatile、synchronized、Lock等同步工具和關鍵字實現原理都用到了JMM
3.重排序、內存可見性、原子性
面試題10:什麼是happens-before,規則有哪些
面試題11:講一講volatile關鍵字
- volatile是一種同步機制,比synchronized或者Lock相關類更輕量,由於使用volatile並不會發生上下文切換等開銷很大的行爲。而加鎖時對象鎖會阻塞開銷大。
- 可見性,若是一個變量別修飾成volatile,那麼JVM就知道了這個變量可能會被併發修改;
- 不能保證原子性
面試題12:volatile的適用場合及做用
-
做用
- 1.保證可見性 2.禁止指令重排序(單例雙重鎖時)
-
適合場景
- 適用場合1:boolean flag,布爾具備原子性,可再由volatile保證其可見性
- 適用場合2:做爲刷新以前變量的觸發器
- 但不適合非原子性操做如:a++等
面試題13:volatile和synchronized的異同
- 1 性能開銷方面: 鎖開銷更大,volatile無加鎖阻塞開銷
2 做用方面:volatile只能保證可見性,鎖既能保證可見性,又能保證原子性
面試題14:什麼是內存可見性問題,爲何存在
- 多線程下,一個線程修改共享數據後,其它線程可否感知到修改了數據的線程的變化
- CPU有多級緩存,致使讀的數據過時,各處理機有獨自的緩存未及時更新時,與主存內容不一致
面試題15:主內存和本地內存的關係是什麼
- Java 做爲高級語言,屏蔽了CPU cache等底層細節,用 JMM 定義了一套讀寫內存數據的規範,雖然咱們再也不須要關心一級緩存和二級緩存的問題,可是,JMM 抽象了主內存和本地內存的概念。
- 線程擁有本身的本地內存,並共享主內存的數據;線程讀寫共享數據也是經過本地內存交換的,因此才致使了可見性問題。
面試題16:什麼是原子操做,Java的原子操做有哪些
-
原子操做
- 一系列的操做,要麼所有執行成功,要麼所有不執行,不會出現執行一半的狀況,是不可分割的。
-
1)除long和double以外的基本類型(int, byte, boolean, short, char, float)的"賦值操做"
-
2)全部"引用reference的賦值操做",不論是 32 位的機器仍是 64 位的機器
-
3)java.concurrent.Atomic.* 包中全部類的原子操做
面試題17:long 和 double 的原子性你瞭解嗎
- 在32位上的JVM上,long 和 double的操做不是原子的,可是在64位的JVM上是原子的。
- 在32位機器上一次只能讀寫32位;而浮點數、long型有8字節64位;要分高32位和低32位兩條指令分開寫入,相似彙編語言浮點數乘法分高低位寄存器;64位不用分兩次讀寫了
面試題18:生成對象的過程是否是原子操做
- 不是,對象生成會生成分配空間、初始化、賦值,三條指令,有可能會被重排序,致使空指針
面試題19:區分JVM內存結構、Java內存模型 、Java對象模型
面試題20:什麼是重排序
- 指令實際執行順序和代碼在java文件中的順序不一致
- 重排序的好處:提升處理速度,包括編譯器優化、指令重排序(局部性原理)
死鎖
面試題1:寫一個必然死鎖的例子
面試題2:生產中什麼場景下會發生死鎖
- 併發中多線程各執己見:當兩個(或更多)線程(或進程)相互持有對方所須要的資源,又不主動釋放,致使全部人都沒法繼續前進,致使程序陷入無盡的阻塞,這就是死鎖。
面試題3:發生死鎖必須知足哪些條件
- 1.互斥
- 2.請求和保持
- 3.不可剝奪
- 4.存儲循環等待鏈
面試題4:如何用工具定位死鎖
- 1.jstack命令在程序發生死鎖後,進行堆棧分析出死鎖線程
- 2.ThreadMXbean 程序運行中發現死鎖,一旦發現死鎖可讓用戶去打日誌
面試題5:有哪些解決死鎖問題的策略
-
1.死鎖語法,不讓死鎖發生
-
2.死鎖避免
-
3.死鎖檢查與恢復
- 適用資源請求分配圖,一段時間內檢查死鎖,有死鎖就恢復策略,採用恢復策略;
- 恢復方法:進程終止 、資源剝奪
-
4.鴕鳥策略(忽略死鎖)
面試題6:死鎖避免策略和檢測與恢復策略的主要思路是什麼
面試題7:講一講經典的哲學家就餐問題,如何解決死鎖
-
何時死鎖
- 哲學家各拿起本身左手邊的筷子,又去請求拿右手邊筷子循環請求時而阻塞
-
如何解決死鎖
- 1.一次兩隻筷子,造成原子性操做
- 2.只容許4我的拿有筷子
面試題8:實際開發中如何避免死鎖
- 設置超時時間
- 多使用併發類而不是本身設計鎖
- 儘可能下降鎖的使用粒度:用不一樣的鎖而不是一個鎖,鎖的範圍越小越好
- 避免鎖的嵌套:MustDeadLock類
- 分配資源前先看能不能收回來:銀行家算法
- 儘可能不要幾個功能用同一把鎖:專鎖專用
- 給你的線程起個有意義的名字:debug和排查時事半功倍,框架和JDK都遵照這個最佳實踐
面試題9:什麼是活躍性問題?活鎖、飢餓和死鎖有什麼區別
-
活鎖
-
飢餓
- 當線程須要某些資源(例如CPU),可是卻始終得不到,可能緣由是飢餓線程的優先級太低