天天看點

一個JDBC驅動注冊死鎖問題總結

群裡有個大神(你假笨)再講解工作中碰到的一個死鎖問題.

情況是這樣的:

項目碰到多線程初始化jdbc驅動時,産生死鎖,如下執行個體所示: (我的環境: jdk1.7.0_45, msql_jdbc:mysql-connector-java-5.1.29)

發現程式出現死鎖,以下是jconsole截圖

一個JDBC驅動注冊死鎖問題總結

線程a,可以看到卡死在mysql driver的static代碼塊上.

一個JDBC驅動注冊死鎖問題總結

線程b,可以看到也是卡死在drivermanager的static代碼塊上,

下面是發生死鎖時卡死的大概代碼位置

線程a:靜态代碼塊主動注冊驅動

一個JDBC驅動注冊死鎖問題總結

線程b:靜态代碼塊,主動加載所有驅動.

一個JDBC驅動注冊死鎖問題總結

說明:這個方法會掃描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