線程安全
1,什麼是線程安全?線程安全:多個線程同時訪問公共資源數據,運行同一段代碼,並且每次執行的結果都與預期結果一致,既爲線程安全的。如果執行的結果存在不確定性,既爲線程不安全的。
2,servlet是線程安全嗎?
servlet不是線程安全的,因爲每個servlet在Tomcat是單例的,當多個HTTP請求同時請求同一個servlet時,多個請求對應的線程將併發調用Servlet的service()方法,此時,如果在Servlet中定義了實例變量或靜態變量,那麼可能會發生線程安全問題。
3,同步有幾種實現方法?
1,同步方法(synchronized關鍵字修飾的方法);
2,同步代碼塊(synchronized關鍵字修飾的語句塊);
3,使用ReentrantLock可重入鎖實現線程同步;
4,線程排隊執行
總結就是加鎖和隊列
5,volatile有什麼用?能否用一句話說明下volatile的應用場景?
volatile的作用有兩點,內存可見性和防止指令重排序。
內存可見性:根據JMM模型,每個線程內部的工作內存中,保存有公共變量的副本,而volatile的作用就是使工作內存中的變量即時刷新到主內存中。
防止指令重排序:根據happens-before原則,對volatile變量的寫操作 happens-before於對該變量的讀。
volatile的使用場景:
1,狀態標誌;2,獨立觀察;3,開銷較低的“讀-寫鎖”策略
6,請說明下java的內存模型及其工作流程。
Java的內存模型JMM(Java Memory Model)JMM主要是爲了規定了線程和內存之間的一些關係。根據JMM的設計,系統存在一個主內存(Main Memory),Java中所有實例變量都儲存在主存中,對於所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存由緩存和堆棧兩部分組成,緩存中保存的是主存中變量的副本,緩存可能並不總和主存同步,也就是緩存中變量的修改可能沒有立刻寫到主存中;堆棧中保存的是線程的局部變量,線程之間無法相互直接訪問堆棧中的變量。
從上圖來看,線程A與線程B之間如要通信的話,必須要經歷下面2個步驟:
- 首先,線程A把本地內存A中更新過的共享變量刷新到主內存中去。
- 然後,線程B到主內存中去讀取線程A之前已更新過的共享變量。
重排序分編譯器重排序和處理器重排序,代碼在編譯時編譯器會對代碼進行優化重排序。處理器在執行指令時,根據指令並行技術,如果兩條指令不存在依賴性,處理器可以改變語句對應指令的執行順序;而且由於處理器使用緩存讀寫,這使得加載和存儲操作看上去可能是在亂序執行。
在執行程序時爲了提高性能,編譯器和處理器常常會對指令做重排序。重排序分三種類型:
- 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
- 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
- 內存系統的重排序。由於處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操作看上去可能是在亂序執行。
從java源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:
上述的1屬於編譯器重排序,2和3屬於處理器重排序。這些重排序都可能會導致多線程程序出現內存可見性問題。對於編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對於處理器重排序,JMM的處理器重排序規則會要求java編譯器在生成指令序列時,插入特定類型的內存屏障(memory barriers,intel稱之爲memory fence)指令,通過內存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。
併發容器和框架
1,如何讓一段程序併發的執行,並最終彙總結果?
使用CyclicBarrier 在多個關口處將多個線程執行結果彙總 ;CountDownLatch 在各線程執行完畢後向總線程彙報結果。
2,如何合理的配置java線程池?如CPU密集型的任務或IO密集型的任務,基本線程池應該配置多大?
CPU密集型任務可以少配置線程數,大概和機器的cpu核數相當,可以使得每個線程都在執行任務;IO密集型時,大部分線程都阻塞,故需要多配置線程數,2*cpu核數
3,用有界隊列好還是無界隊列好?任務非常多的時候,使用什麼阻塞隊列能獲取最好的吞吐量?
有界隊列和無界隊列的配置需區分業務場景,一般情況下配置有界隊列,在一些可能會有爆發性增長的情況下使用無界隊列。
任務非常多時,使用非阻塞隊列使用CAS操作替代鎖可以獲得好的吞吐量。
4,如何使用阻塞隊列實現一個生產者和消費者模型?請寫代碼。TODO
5,多讀少寫的場景應該使用哪個併發容器,爲什麼使用它?比如你做了一個搜索引擎,搜索引擎每次搜索前需要判斷搜索關鍵詞是否在黑名單裏,黑名單每天更新一次。
答:1)CopyOnWriteArrayList這個容器適用於多讀少寫…
2)讀寫並不是在同一個對象上。在寫時會大面積複製數組,所以寫的性能差,在寫完成後將讀的引用改爲執行寫的對象
Java中的鎖
1,如何實現樂觀鎖(CAS)?如何避免ABA問題? 1.CAS原語有三個值,一個是內存值,一個是期望值,一個是寫入值。 在不加鎖的情況下寫入時,每次讀取內存值,然後跟預期值比對,如果比對失敗,反覆的讀和比對,直到成功。在CAS原語是一個原子操作,如果寫入時,內存值發生改變,則寫入值失敗。
2.帶參數版本來避免aba問題,在讀取和替換的時候進行判定版本是否一致
2,什麼場景下可以使用volatile替換synchronized?
只需要保證共享資源的可見性的時候可以使用volatile替代,synchronized保證可操作的原子性一致性和可見性。 volatile適用於新值不依賴於就值的情形
3,什麼是可重入鎖(ReentrantLock)?
ReentrantLock 相對於固有鎖synchronized,同樣是可重入的,在某些vm版本上提供了比固有鎖更高的性能,提供了更豐富的鎖特性,比如可中斷的鎖,可等待的鎖,平等鎖以及非塊結構的加鎖。從代碼上儘量用固有鎖,vm會對固有鎖做一定的優化,並且代碼可維護和穩定。只有在需要ReentrantLock的一些特性時,可以考慮用ReentrantLock實現。
4,ReentrantLock 和synchronized比較。來自《java併發編程實戰》
1.爲什麼JUC框架出現LOCK?
ReentrantLock並不是替代synchronized的方法,而是當內置鎖不適用時,作爲一種可選的高級功能。
2.那麼Synchronized有哪些缺點?
①. 只有一個condition與鎖相關聯,這個condition是什麼?就是synchronized對針對的對象鎖。
②. synchronized無法中斷一個正在等待獲得鎖的線程,也即多線程競爭一個鎖時,其餘未得到鎖的線程只能不停的嘗試獲得鎖,而不能中斷。這種情況對於大量的競爭線程會造成性能的下降等後果。
3.我們面對ReentrantLock和synchronized改如何選擇?
Synchronized相比Lock,爲許多開發人員所熟悉,並且簡潔緊湊,如果現有程序已經使用了內置鎖,那麼儘量保持代碼風格統一,儘量不引入Lock,避免兩種機制混用,容易令人困惑,也容易發生錯誤。
在Synchronized無法滿足需求的情況下,Lock可以作爲一種高級工具,這些功能包括“可定時的、可輪詢的與可中斷的鎖獲取操作,公平隊列,以及非塊結構的鎖”否則還是優先使用Synchronized。
最後,未來更可能提升Synchronized而不是Lock的性能,因爲Synchronized是JVM的內置屬性,他能執行一些優化,例如對線程封閉的鎖對象的鎖消除優化,通過增加鎖的粒度來消除內置鎖的同步,而如果基於類庫的鎖來實現這些功能,則可能性不大。
5,讀寫鎖可以用於什麼應用場景?(ReentrantReadWriteLock)
區分讀和寫,處於讀操作時,可以允許多個線程同時獲得讀操作。但是同一時刻只能有一個線程可以獲得寫鎖。其它獲取寫鎖失敗的線程都會進入睡眠狀態,直到寫鎖釋放時被喚醒。
注意:寫鎖會阻塞其它讀寫鎖。當有一個線程獲得寫鎖在寫時,讀鎖也不能被其它線程獲取;寫優先於讀,當有線程因爲等待寫鎖而進入睡眠時,則後續讀者也必須等待
適用於讀取數據的頻率遠遠大於寫數據的頻率的場合。
6,什麼時候應該使用可重入鎖?
場景1:如果發現該操作已經在執行中則不再執行(有狀態執行)
a、用在定時任務時,如果任務執行時間可能超過下次計劃執行時間,確保該有狀態任務只有一個正在執行,忽略重複觸發。
b、用在界面交互時點擊執行較長時間請求操作時,防止多次點擊導致後臺重複執行(忽略重複觸發)。
以上兩種情況多用於進行非重要任務防止重複執行,(如:清除無用臨時文件,檢查某些資源的可用性,數據備份操作等)
場景2:如果發現該操作已經在執行,等待一個一個執行(同步執行,類似synchronized)
這種比較常見大家也都在用,主要是防止資源使用衝突,保證同一時間內只有一個操作可以使用該資源。但與synchronized的明顯區別是性能優勢(伴隨jvm的優化這個差距在減小)。同時Lock有更靈活的鎖定方式,公平鎖與不公平鎖,而synchronized永遠是公平的。
這種情況主要用於對資源的爭搶(如:文件操作,同步消息發送,有狀態的操作等)
ReentrantLock默認情況下爲不公平鎖
不公平鎖與公平鎖的區別:
公平情況下,操作會排一個隊按順序執行,來保證執行順序。(會消耗更多的時間來排隊)
不公平情況下,是無序狀態允許插隊,jvm會自動計算如何處理更快速來調度插隊。(如果不關心順序,這個速度會更快)
場景3:如果發現該操作已經在執行,則嘗試等待一段時間,等待超時則不執行(嘗試等待執行)
這種其實屬於場景2的改進,等待獲得鎖的操作有一個時間的限制,如果超時則放棄執行。
用來防止由於資源處理不當長時間佔用導致死鎖情況(大家都在等待資源,導致線程隊列溢出)
場景4:如果發現該操作已經在執行,等待執行。這時可中斷正在進行的操作立刻釋放鎖繼續下一操作。
synchronized與Lock在默認情況下是不會響應中斷(interrupt)操作,會繼續執行完。lockInterruptibly()提供了可中斷鎖來解決此問題。(場景2的另一種改進,沒有超時,只能等待中斷或執行完畢)
這種情況主要用於取消某些操作對資源的佔用。如:(取消正在同步運行的操作,來防止不正常操作長時間佔用造成的阻塞)