java多執行緒中常用指令
------------恢復內容開始------------
一、寫在前面
好久沒寫部落格了,這不快畢業了,應該會重新開始更新部落格了。這次主要介紹檢視執行緒狀態等一系列常見指令,包括有jps、vmstat、jstack、javap、以及如何檢視java對應的彙編程式碼。
二、情景
依據假設情景來說明為啥以及如何使用這些指令。現在你是個初出茅廬的java程式設計師,你一看現在的業務用的單執行緒處理,大吃一斤,馬上提出我要優化,改成多執行緒,然後寫下了如下程式碼。然後你把程式碼提交,上線,準備感受多執行緒的速度。然而業務上線後直接癱瘓,這時你該怎麼辦。
package com.wx.ch1;public class DeadLockDemo { private static String A = "A"; private static String B = "B"; public static void main(String[] args){ new DeadLockDemo().DeadLock(); } private void DeadLock(){ Thread t1 = new Thread(new Runnable() { @Overridepublic void run() { synchronized (A){ try { Thread.currentThread().sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } synchronized (B){ System.out.println("get A B"); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (B){ synchronized (A){ System.out.println("get B A"); } } } }); t1.start(); t2.start(); } }
三、發現問題
首先,你希望你能夠獲得現在這些執行緒所處狀態,然後判斷髮生了什麼,jstack指令可以幫到你。
3.1 jstack指令
該指令常被用於dump出指定程序下,所有執行緒的資訊。指令格式如下:
sudo -u 使用者名稱 jstack指令位置 程序id >檔名
其中jstack指令位置由你安裝java的位置決定,你可以通過echo $JAVA_HOME、whereis java等命令進行查詢。檔案名錶示輸出重定向的結果,可以把結果存入對應的檔案方便檢視。
我使用的指令樣例:
sudo -u wx /home/wx/java/jdk1.8/bin/jstack 32763 >/home/wx/result
我們先來看看結果,結果很明顯表明兩個執行緒都被阻塞了,互相等待對方釋放鎖。
3.2 jps指令
如果你按照jstack的指令嘗試打印出執行緒資訊,你會發現缺少指令中的程序id。那麼該如何查詢這些執行中的java程序id?常見的查詢程序id指令是ps,java為我們提供了
專門的查詢執行在jvm上的java程序id指令,即jps指令。
指令格式:jps
3.3 vmstat
現在你通過上面兩個指令的組合發現了死鎖的問題,你改寫了程式碼,成功執行在伺服器上了,下一步你要做的是精益求精,開始優化!當然優化是件很複雜的事情,這邊只提通過檢視切換上下文次數的指令vmstat來判斷當前多執行緒程式碼執行情況。
指令格式:vmstat (後面可以接引數 可自行vmstat -h 檢視)
最常見的為 vmstat -t 1 (每間隔一秒時間列印一次)
vmstat -t 1
圖中cs(Content Swich)列為即為上下文切換次數。
3.4 javap & java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
這兩個指令被用於檢視對應的java位元組碼以及彙編程式碼。在本文的場景中,我們通過javap以及虛擬機器啟動命令列引數,檢視並驗證volatile關鍵字的底層實現。
在多個材料中均提及帶有volatuke關鍵字的變數在修改時,對應生成的彙編程式碼帶有lock字首,我們要做的就是驗證這一點。
驗證程式碼:
package com.wx.ch1; public class RecordExample { int a =0; volatile boolean flag = false; public void writer(){ flag = true; for(int i=0;i<1000000;i++){ } a = 1; } public boolean reader(){ if(flag){ int i = a*a; System.out.println(i); return true; } return false; } public static void main(String[] args) { RecordExample re = new RecordExample(); Thread t1 = new Thread(new Runnable() { @Override public void run() { re.writer(); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (;;){ if (re.reader()){ break; } } } }); t1.start(); t2.start(); } }
程式碼的邏輯無需在意,只需要關注該程式碼對於volatile宣告的變數flag進行了修改。
通過指令:javap -v RecordExample(視具體路徑需要補充包名)
得到關鍵位元組碼如下,可以看到flags變數宣告帶有ACC_VOLATILE標識,該標識對應於jvm中的原始碼方法,該方法負責生成適配不同機器的作業系統,轉換成同等語義的彙編程式碼,即在該程式碼中,該位元組碼對應的
彙編程式碼,被插入了lock字首的指令。
通過虛擬機器指令檢視進一步檢視位元組碼對應的彙編程式碼
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 類名
在實際使用該指令檢視彙編程式碼的過程中,遇到了不少問題。
1)獲得的彙編程式碼為16進位制格式,無法理解程式碼含義。經過判斷認為是jdk版本的原因導致,之前所使用的版本為open-jdk17,在替換成oracle-jdk1.8以後,可以見到明文形式的彙編程式碼。
2)在更換完版本以後執行該指令發現缺少對應的元件hsdis-amd64.so。如果沒有下載並新增該元件,執行指令時會顯示can not load hsdis-amd64.so。該元件是ubuntu下的格式,windows下的版本不同。windows版本網路上
很多,直接搜尋hsdis dll關鍵字。這裡提供一下linux上64位的該元件下載地址:連結: https://pan.baidu.com/s/1FkDyXDhoPk3XLfsNx3U3Rw 提取碼: 1ta3 複製這段內容後開啟百度網盤手機App,操作更方便哦。
3)完成下載以後,在linux作業系統中需要將該檔案貼上複製到java安裝目錄下的/jre/lib/amd64,我的貼上路徑為:/home/wx/java/jdk1.8/jre/lib/amd64。同意可以通過echo $JAVA_HOME查詢jdk安裝目錄,前提是配置了環境變數。
在完成上述一系列操作以後,就可以在控制檯敲上述指令觀察得到對應的彙編程式碼。在這裡我們通過開發工具idea完成相關虛擬機器指令配置,直接在idea中輸出對應的彙編程式碼。
對應配置截圖如下
如果沒有虛擬機器的配置項,則點選modify options增加vm配置 ,勾選add VM options:
生成彙編程式碼截圖:可以看到對應指令帶有的lock字首,註解部分顯示是操作volatile變數。
接著我們將程式碼flag變數宣告的volatile刪除,宣告為一般變數,再次檢視對應的彙編程式碼,會發現不再有帶有lock字首的對於變數值修改的指令。
四、最後
總結一下,介紹了與多執行緒程式設計相關的一些基礎指令,首先通過jps配合jstack可以檢視當前執行緒的狀態資訊;接著,可以通過vmstat指令去檢視執行緒的導致的上下文切換次數,該指標是反應多執行緒效能的一個重要指標,過多的上下文切換會影響執行緒的執行速度。最後,介紹了檢視java對應位元組碼與彙編程式碼的指令。
------------恢復內容結束------------