1.syncronized底層原理——悲觀鎖
synchronized有物件鎖和類鎖兩種,多個執行緒中只有一個執行緒可以獲取物件鎖,其他執行緒都會處於阻塞狀態
synchronized是底層是基於monitor實現的。monitor是C++編寫的jvm物件,主要分為owner(這個只會存一個執行緒的資訊,記錄當前鎖被哪個執行緒獲取了)、entrySet(這個是一個佇列,記錄沒有搶到鎖的執行緒,他們都是處於block狀態的)、waitSet(記錄呼叫了wait方法的執行緒)
monitor屬於重量級鎖,在jdk1.6之後又引入了偏向鎖和輕量級鎖
(更難的聽不懂了,涉及JVM)
2.談談JMM(java memory model)java記憶體模型
JMM定義共享記憶體中,多執行緒程式的讀寫規範。JMM把記憶體分為工作記憶體和主記憶體;執行緒和執行緒之間是隔離的,但是可以透過執行緒1把訊息從工作記憶體傳送給主記憶體,再由主記憶體把訊息推送給執行緒2,實現執行緒之間的互動
個人理解工作記憶體≈棧記憶體Stack,而主記憶體≈堆記憶體Heap
3.談談CAS(compare and swap)——樂觀鎖 | 自旋鎖
cas是一種樂觀鎖思想,在無鎖情況下保證執行緒操作共享資料的原子性。
java中的cas是基於作業系統的cas實現的,屬於native方法;工作記憶體從主記憶體讀取到的資料,我們需要把舊資料修改。當要修改時我們會再次比對工作記憶體的舊資料和主記憶體的資料,如果一致,那麼就可以修改;如果不一致,那就要發生自旋:工作記憶體再次去主記憶體讀取資料並重覆上述方法。
在鎖競爭不激烈的情況下cas是比synchronized效率要好的
4.樂觀鎖和悲觀鎖的區別
樂觀鎖允許別的執行緒修改變數,如果別的執行緒修改了變數那就自旋
悲觀鎖不允許別的執行緒修改變數,每次上鎖的時候別的執行緒都會阻塞
5.關鍵字volatile
可見性:線上程中使用volatile修飾共享變數,可以防止編譯器最佳化的發生。
下述例子中,下方的執行緒while(flag)被JIT(即時編譯器)最佳化為了while(true),從而導致上面的執行緒修改flag下面的執行緒卻讀不到結果,這時我們可以給flag新增volatile來阻止編譯器最佳化
static boolean flag=true; public static void main(String[] args) throws InterruptedException { new Thread(()->{ try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } flag=false; System.out.println(flag+"已經修改"); }).start(); new Thread(()->{ int i=0; while (flag){ i++; } System.out.println(flag+"_"+i); }).start(); }
禁止指令重排序:阻止一些讀寫操作越過屏障發生一些意外問題(寫是防止當前指令越過上方指令,讀是防止當前指令越過下方指令)
6.什麼是AQS
AQS是抽象佇列同步器,與synchronized不同的是他是由java實現的,並且鎖激烈競爭的情況下有多種解決方案而不是像synchronized一樣只有重量級鎖
透過volatile保證state的可見性,然後當某一執行緒持有state後改變其數值,後續等待執行緒進入佇列,依據佇列完成排隊
如何保證原子性:利用CAS在搶state時保證原子性
7.什麼是公平鎖:
新來的執行緒和佇列中等待的執行緒爭搶鎖資源
非公平鎖:
新來的執行緒只能進入佇列的tail,只有佇列的head能獲取鎖資源
注意,一般情況下公平鎖的效率沒有非公平鎖高,公平鎖的吞吐量較低
8.ReentrantLock可重入鎖的原理
reentrantLock是基於AQS佇列+CAS實現的,有無參建構函式和是否使用公平鎖的建構函式,其中無參情況下預設是false
9.synchronized和lock的區別
- 語法上:synchronized是關鍵字,底層由C++實現,來自jvm;lock是介面,底層java原生,來自jdk。
- 功能上:都是悲觀鎖,互斥同步;lock有比synchronized更豐富的功能,lock除了正常鎖以外還有公平鎖,可打斷鎖,超時鎖,多條件鎖(這個類似於await和notify),並且讀寫鎖就是是lock。但是synchronized在退出程式碼塊後會自動釋放,而lock需要手動呼叫unlock來釋放鎖。
- 效能上:競爭不激烈的時候synchronized因為有偏向鎖和輕量級鎖優於lock,在競爭激烈的情況下lock具有更好的效能
10.死鎖的產生條件
一個執行緒同時獲取多把鎖
如何檢查死鎖的發生:
- 先用jps檢視當前程序,程序裡如果有死鎖會顯示xxx執行緒為deadlock,再用jstack檢視詳細資訊
- 在jdk目錄中找jconsole,此時能選擇你在jps裡檢視的所有程序
11.講講concurrentHashMap
JDK1.7使用segment陣列+hashmap實現了concurrenthashmap;透過對key的hash計算找到對應的segment陣列,然後透過reentrantLock鎖住具體的segment陣列,高併發的時候會透過CAS自旋鎖來保證資料安全,然後再透過hash定位具體位置。劣勢就是多個key如果hash相同的話他們就會阻塞。segment陣列不能進行擴容,這也導致1.7的concurrenthashmap的效能不好
JDK1.8版本下采用CAS和synchronized對HashMap進行了增強。
利用CAS來控制擴容的實現,為頭結點上鎖,暫停其他執行緒的put操作,避免了多執行緒的插入衝突;在空位置新增節點時,也會採用CAS確保併發下能插入成功
synchronized只鎖連結串列或者紅黑樹的首節點,保證陣列節點只能被一個執行緒修改,降低了鎖的顆粒度並且只要hash不衝突就不會有效率問題;在擴容時透過synchronized保證了資料遷移的一致性和完整性
12.java如何保證多執行緒的執行安全
原子性:一個執行緒在CPU中的操作是不可暫停的,是無法中斷的。這裡我們是透過鎖解決,保證資料正確
可見性:一個執行緒對共享變數的修改對另一個執行緒可見。這裡我們使用volatile保證可見性
有序性:處理器為了提高程式執行效率,對輸入程式碼進行最佳化,不能保證程式中各個語句的執行順序同程式碼中的順序一致,但是能保證程式最終執行結果和程式碼順序執行的結果一致。這裡我們使用volatile保證有序性