群裡有個大神(你假笨)再講解工作中碰到的一個死鎖問題.
情況是這樣的:
項目碰到多線程初始化jdbc驅動時,産生死鎖,如下執行個體所示: (我的環境: jdk1.7.0_45, msql_jdbc:mysql-connector-java-5.1.29)
發現程式出現死鎖,以下是jconsole截圖

線程a,可以看到卡死在mysql driver的static代碼塊上.
線程b,可以看到也是卡死在drivermanager的static代碼塊上,
下面是發生死鎖時卡死的大概代碼位置
線程a:靜态代碼塊主動注冊驅動
線程b:靜态代碼塊,主動加載所有驅動.
說明:這個方法會掃描classpath: /meta-inf/services/java.sql.driver 檔案,該檔案存放的是driver的具體實作,例如mysql jdbc jar中該檔案内容為文本: com.mysql.jdbc.driver
也就是它會找出classpath下所有的jdbc驅動實作類,然後他會調用(省略了很多代碼)
我大概說下吧:
初始化java類和接口需要保證線程安全, 因為有可能同一時間有多個線程同時初始化某一類或接口,但是語言要求一個類對于一個classloader隻能初始化一次.
那麼虛拟機是如何保證的呢? 沒錯, 加鎖(-.-!
虛拟機給一個已經被加載的class定義了四個狀态: 1.沒有初始化, 2.正在被初始化, 3.已經被初始化, 4.初始化報錯(比如static代碼塊内代碼抛異常了).
當一個類發現他要初始化一個class時, 比如c, 它會去申請一把鎖 lc, 擷取到lc之後(沒擷取就阻塞), 他就開始檢視狀态了,
主要過程就是上面的(遞歸情況自己看去.....................)
好, 現在我們來對着上面的死鎖例子來分析.
假設這樣一個場景(以下步驟按時間順序):
1.線程a: 調用class.forname方法,第二個參數為true,表示如果類沒有初始化則初始化,
2.線程a: com.mysql.jdbc.driver準備初始化, 擷取鎖(ldriver), 當剛剛擷取到鎖, (也就是剛剛進入到static代碼塊中,還沒執行任何操作.)
3.線程b: 開始執行,因為調用了.java.sql.drivermanager.getlogintimeout()這個方法,然後得保證先加載drivermanager類,
4.線程b: 加載drivermanager.class , 擷取鎖(ldrivermanager), 執行static代碼塊, 然後找到所有的jdbc驅動實作class(其中包含com.mysql.jdbc.driver)
5.線程b: 因為調用了com.mysql.jdbc.driver.newinstance(), 是以他要保證com.mysql.jdbc.driver已經被初始化, 是以他去申請擷取鎖(ldriver),
6.線程a: 繼續執行,然後準備執行java.sql.drivermanager.registerdriver(new driver()); 是以要保證drivermanager已經被初始化, 于是申請鎖(ldrivermanager)
于是.......................................................................................................死鎖了......
是以還是 避免手動的調用class.forname()加載驅動,特别是多線程情況.
問題并不難,難的是碰到問題後去查找.
碰到這個問題,我想到的就是通過堆棧去找代碼,然後對照源碼一步步的分析猜測各種情況,
但是這樣不能百分百的找出問題, 而且對個人的技術也有一定的要求.
但是大神就是大神,通過分析記憶體,檢視虛拟機源碼,反彙編,等等操作,雖然過程繁瑣了點,但是無敵啊~~~~膜拜ing