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

羣裏有個大神(你假笨)再講解工做中碰到的一個死鎖問題.html

這個是大神後來總結的文章:http://lovestblog.cn/blog/2014/07/08/jdk-sql-deadlock/  java

狀況是這樣的:mysql

項目碰到多線程初始化JDBC驅動時,產生死鎖,以下實例所示:   (個人環境: JDK1.7.0_45,  msql_jdbc:mysql-connector-java-5.1.29)sql

public class Temp {
	public static void main(String[] args) throws Exception {
		Thread a = new Thread(new ThreadA());
		Thread b= new Thread(new ThreadB());
		a.start();
		b.start();
	}
}

class ThreadA implements Runnable{
	@Override
	public void run() {
		try {
			Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

class ThreadB implements Runnable{
	@Override
	public void run() {
		java.sql.DriverManager.getLoginTimeout();//這個調用只是爲了加載DriverManager類
	}
}

發現程序出現死鎖,如下是jconsole截圖安全

線程A,能夠看到卡死在mysql Driver的static代碼塊上.
多線程


線程B,能夠看到也是卡死在DriverManager的static代碼塊上,oracle


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


線程A:靜態代碼塊主動註冊驅動spa


線程B:靜態代碼塊,主動加載全部驅動.線程

說明:這個方法會掃描classpath: /META-INF/services/java.sql.Driver 文件,該文件存放的是Driver的具體實現,例如mysql JDBC jar中該文件內容爲文本: com.mysql.jdbc.Driver

也就是它會找出classpath下全部的jdbc驅動實現類,而後他會調用(省略了不少代碼)

Class<?> c = Class.forName(cn, false, loader);   //裝載驅動實現類,可是不初始化
S p = service.cast(c.newInstance());             //判斷驅動實現類是否是實現了java.sql.Driver接口,(serveic == Driver.class)



要說明死鎖會出現的緣由,咱們得先來了解下類初始化的過程, 具體見oracle文檔:http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2

我大概說下吧:

初始化Java類和接口須要保證線程安全, 由於有可能同一時間有多個線程同時初始化某一類或接口,可是語言要求一個類對於一個classloader只能初始化一次.

那麼虛擬機是如何保證的呢? 沒錯, 加鎖(-.-!

虛擬機給一個已經被加載的class定義了四個狀態:  1.沒有初始化, 2.正在被初始化, 3.已經被初始化, 4.初始化報錯(好比static代碼塊內代碼拋異常了).

當一個類發現他要初始化一個class時, 好比C, 它會去申請一把鎖 LC, 獲取到LC以後(沒獲取就阻塞), 他就開始查看狀態了,

    若是當前狀態是1(沒有初始化), 就將狀態改爲2(正在初始化), 而後釋放鎖,而後開始初始化.

    若是當前狀態是2(正在初始化), 就釋放鎖,而後block,等待被喚醒(當有線程完成初始化後,會獲取鎖,將狀態改爲3(初始化完成),而後喚醒阻塞在這裏的線程)

    若是當前狀態是3(已被初始化), 就釋放鎖,而後.................................直接用啥~~~~差點沒反應過來......

    若是當前狀態是4(異常狀態),    就釋放鎖,而後拋個NoClassDefFoundError.

主要過程就是上面的(遞歸狀況本身看去.....................)



好, 如今咱們來對着上面的死鎖例子來分析.

假設這樣一個場景(如下步驟按時間順序):

  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)

               經過調用驅動實現class.newInstance()方法來判斷是否是實現了java.sql.Driver接口, (即:com.mysql.jdbc.Driver.newInstance())

  5. 線程B:  由於調用了com.mysql.jdbc.Driver.newInstance(), 因此他要保證com.mysql.jdbc.Driver已經被初始化, 因此他去申請獲取鎖(LDriver),

               可是這個時候鎖(LDriver)被線程A 鎖佔用着,因此阻塞.

  6. 線程A:  繼續執行,而後準備執行java.sql.DriverManager.registerDriver(new Driver()); 因此要保證DriverManager已經被初始化, 因而申請鎖(LDriverManager)

               可是這個時候鎖(LDriverManager)被線程B佔用着,因此阻塞.


因而.......................................................................................................死鎖了......


因此仍是 避免手動的調用Class.forName()加載驅動,特別是多線程狀況.


問題並不難,難的是碰到問題後去查找.

碰到這個問題,我想到的就是經過堆棧去找代碼,而後對照源碼一步步的分析猜想各類狀況,

可是這樣不能百分百的找出問題, 並且對我的的技術也有必定的要求.

可是大神就是大神,經過分析內存,查看虛擬機源碼,反彙編,等等操做,雖然過程繁瑣了點,可是無敵啊~~~~膜拜ing

相關文章
相關標籤/搜索