摘要:問題描述近期項目需要從虛擬機環境遷移到容器環境,其中有一個項目在遷移到容器環境之后的兩天之內出現了次死鎖的問題,部分關鍵日志如下日志還是挺明顯的,線程獲得了鎖,等待獲取而正好相反,從而導致死鎖問題分析以上的錯誤
問題描述
近期項目需要從虛擬機環境遷移到容器環境,其中有一個項目在遷移到容器環境之后的兩天之內出現了2次“死鎖(deadlock)”的問題,部分關鍵日志如下:
Found one Java-level deadlock: ============================= "DefaultMessageListenerContainer-9": waiting to lock monitor 0x00007fde3400bf38 (object 0x00000000dda358d0, a oracle.jdbc.driver.T4CConnection), which is held by "DefaultMessageListenerContainer-7" "DefaultMessageListenerContainer-7": waiting to lock monitor 0x00007fdea000b478 (object 0x00000000dda35578, a oracle.jdbc.driver.T4CConnection), which is held by "DefaultMessageListenerContainer-9" Java stack information for the threads listed above: =================================================== "DefaultMessageListenerContainer-9": at oracle.jdbc.oracore.OracleTypeADT.linearize(OracleTypeADT.java:1280) - waiting to lock <0x00000000dda358d0> (a oracle.jdbc.driver.T4CConnection) at oracle.sql.ArrayDescriptor.toBytes(ArrayDescriptor.java:653) at oracle.sql.ARRAY.toBytes(ARRAY.java:711) - locked <0x00000000dda35578> (a oracle.jdbc.driver.T4CConnection) at oracle.jdbc.driver.OraclePreparedStatement.setArrayCritical(OraclePreparedStatement.java:6049) at oracle.jdbc.driver.OraclePreparedStatement.setARRAYInternal(OraclePreparedStatement.java:6008) - locked <0x00000000dda35578> (a oracle.jdbc.driver.T4CConnection) at oracle.jdbc.driver.OraclePreparedStatement.setArrayInternal(OraclePreparedStatement.java:5963) at oracle.jdbc.driver.OracleCallableStatement.setArray(OracleCallableStatement.java:4833) at oracle.jdbc.driver.OraclePreparedStatementWrapper.setArray(OraclePreparedStatementWrapper.java:114)
"DefaultMessageListenerContainer-7": at oracle.jdbc.oracore.OracleTypeADT.linearize(OracleTypeADT.java:1280) - waiting to lock <0x00000000dda35578> (a oracle.jdbc.driver.T4CConnection) at oracle.sql.ArrayDescriptor.toBytes(ArrayDescriptor.java:653) at oracle.sql.ARRAY.toBytes(ARRAY.java:711) - locked <0x00000000dda358d0> (a oracle.jdbc.driver.T4CConnection) at oracle.jdbc.driver.OraclePreparedStatement.setArrayCritical(OraclePreparedStatement.java:6049) at oracle.jdbc.driver.OraclePreparedStatement.setARRAYInternal(OraclePreparedStatement.java:6008) - locked <0x00000000dda358d0> (a oracle.jdbc.driver.T4CConnection) at oracle.jdbc.driver.OraclePreparedStatement.setArrayInternal(OraclePreparedStatement.java:5963) at oracle.jdbc.driver.OracleCallableStatement.setArray(OracleCallableStatement.java:4833) at oracle.jdbc.driver.OraclePreparedStatementWrapper.setArray(OraclePreparedStatementWrapper.java:114) at
日志還是挺明顯的,線程DefaultMessageListenerContainer-9獲得了鎖0x00000000dda35578,等待獲取0x00000000dda358d0;而DefaultMessageListenerContainer-7正好相反,從而導致死鎖;
問題分析以上的錯誤日志和Oracle的驅動類有關,所以猜測是驅動版本的問題,所以找相關人員分別拉取了虛擬機環境和容器環境的生產Oracle驅動jar包,結果如下:
#虛擬機 [19:38:21 oracle@tomcat-384 lib]$ ls -l ojdbc-1.4.jar -rw-r--r-- 1 oracle oinstall 1378346 Jul 3 2014 ojdbc-1.4.jar #容器 [oracle@7f666c76b7-dx2gq lib]$ ls -l ojdbc6.jar -rw-r--r-- 1 oracle oinstall 2739670 Aug 11 2015 ojdbc6.jar
兩個環境使用了不同的版本,容器使用了高版本(11.2.0.4.0),虛擬機使用的是低版本(10.1.0.5.0);Google查詢了和Oracle驅動相關產生死鎖的問題,查到了Oracle官方有如下文檔:
Java-level deadlock with 11.2
提供給我們的方案是“Upgraded the Oracle JDBC driver from 10.2 to 11.2.”,正好和我們遇到的情況相反,我們是高版本有問題,低版本沒有問題,所以需要進一步分析;
首先找到相關的邏輯代碼類,此處為了更好的看出問題,使用了如下的模擬類,大致如下:
//測試Dao,配置在spring下的單例 public class TestDaoImpl { //共享的兩個ArrayDescriptor private ArrayDescriptor param1Desc; private ArrayDescriptor param2Desc; private String param1; private String param2; private DataSource dataSource; public void callProc(Object param) { // 準備的兩個ARRAY參數 ARRAY param1Array = null; ARRAY param2Array = null; CallableStatement callable = null; Connection conn = null; try { // 從連接池獲取連接 conn = DataSourceUtils.getConnection(dataSource); param1Array = wrapProcParameter1(param, conn); param2Array = wrapProcParameter2(param, conn); callable = conn.prepareCall("{ call testProc " + "(?,?,?)}"); callable.setArray(1, param1Array); callable.setArray(2, param2Array); callable.execute(); } catch (Exception e) { // 異常處理 } finally { // 關閉處理 } } private ARRAY wrapProcParameter1(Object param, Connection conn) throws SQLException { if (null == this.param1Desc) { this.param1Desc = new ArrayDescriptor(this.param1, conn); } //省略 ARRAY array1 = new ARRAY(this.param1Desc, conn, param); return array1; } private ARRAY wrapProcParameter2(Object param, Connection conn) throws SQLException { if (null == this.param2Desc) { this.param2Desc = new ArrayDescriptor(this.param2, conn); } //省略 ARRAY array2 = new ARRAY(this.param2Desc, conn, param); return array2; } }
大致的邏輯是通過從連接池獲取的Connection創建了一個存儲過程,然后給存儲過程設置了兩個ARRAY參數,在創建ARRAY時需要指定相應的ArrayDescriptor,最后執行存儲過程;
產生異常分別在兩次setArray的地方,線程1在setArray1的地方,線程2在setArray2的地方,所有以此為入口分別查看兩個驅動版本相關類:OraclePreparedStatement,ARRAY,ArrayDescriptor以及OracleTypeADT;
首先查看OraclePreparedStatement中調用的setArray,最終會調用如下方法:
在方法setARRAYInternal中使用了connection作為了對象鎖,接下來OraclePreparedStatement會調用ARRAY,然后ARRAY調用ArrayDescriptor,最后ArrayDescriptor在調用OracleTypeADT,為了方便看出問題直接展示OracleTypeADT中使用鎖的地方:
同樣使用connection做為鎖對象,這樣就存在同時需要獲取兩把鎖了,而上面兩把鎖都是connection對象,應該不會出現死鎖,但是深入發現其實OracleTypeADT中的connection對象是從ArrayDescriptor中獲取的,而ArrayDescriptor是一個共享的類變量,這樣在多線程環境下就會出現被賦值不同的connection,從而導致出現死鎖的問題;
大致流程如下:
1.首先線程1獲取conn1,然后線程2獲取conn2;
2.然后線程1創建Array1,同時對共享的ArrayDescriptor1設置connection=conn1;
3.線程1掛起,線程2創建Array1,同時對共享的ArrayDescriptor1設置connection=conn2,對共享的ArrayDescriptor2設置connection=conn2;
4.線程2繼續占用cpu,執行setArray1,這時候都是Array1和ArrayDescriptor1中的鎖都是conn2,所以沒有問題,繼續執行setArray2,在執行完獲取第一把鎖conn2之后,線程2掛起;
5.線程1搶占cpu,對共享的ArrayDescriptor2設置connection=conn1,然后執行setArray1;但此時Array1中的connection是conn1,而ArrayDescriptor1中的connection是conn2,所以出現線程1占用了conn1,等待conn2鎖;
6.此時線程2再次搶到cpu,但是在獲取第二把鎖時,此時ArrayDescriptor2中的connection已經被設置成了conn1,而conn1已經被線程1占有,所以等待獲取conn1;
7.死鎖出現了線程1占有了conn1鎖,等待conn2鎖;線程2占有了conn2鎖,等待conn1鎖;從而導致死鎖發生;
從上面的分析可以看出主要原因是ArrayDescriptor被設置成了類變量,被多個線程所訪問,解決死鎖問題可以把ArrayDescriptor改成局部變量;但是如果僅是業務造成的問題,那應該在驅動ojdbc-1.4中存在同樣的死鎖問題,但是此項目在虛擬機環境中一直沒有出現過問題;繼續看ojdbc-1.4源碼;
同樣分析此驅動版本中的相同類,同上首先查看OraclePreparedStatement中調用的setArray,最終會調用如下方法:
同樣使用了connection作為對象鎖,再看OracleTypeADT,相關代碼如下:
可以看到這里并沒有使用connection作為鎖,而是使用了內置鎖,所以就不會出現死鎖問題;
首先就是在遷移環境時一定要保證相關的依賴公共jar保證版本的一致,就算是低版本,高版本也不一樣保證向下兼容;其次也是最重要的寫業務邏輯時遇到公共變量時一定要謹慎,是否會出現多線程問題;
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74280.html
摘要:的情況下,必然就會死鎖,對吧,接下來怎么用驗證呢切到號線程查看線程棧及棧對象。死鎖原因分析死鎖原因分析要想追究死鎖的原因,只能仔細推敲線程棧線程棧對象。在幾個痙攣過程中進入了另外一個線程池的方法中,希望能得到該池中的鎖對象。一:背景1. 講故事這個月初,星球里的一位朋友找到我,說他的程序出現了死鎖,懷疑是自己的某些寫法導致mongodb出現了如此尷尬的情況,截圖如下:說實話,看過這么多dum...
摘要:于是檢查時發現,拼寫錯誤,應為。第個問題,是真真切切錯誤卸載重要軟件包,導致系統崩潰,修復系統的方法自然也就是利用原鏡像在下把該裝的都裝回去,前提是日志存在,萬幸沒有執行過。 首先問題產生的緣由很簡單,是我一同事在安裝oracle一套軟件時,按照要求需要binutils軟件包的32位版本,然而在Oracle Linux已經裝有64位,按理說是可以安裝i686的,我猜應該是32位的版本低...
閱讀 3156·2021-11-22 09:34
閱讀 2796·2021-09-22 15:28
閱讀 816·2021-09-10 10:51
閱讀 1853·2019-08-30 14:22
閱讀 2273·2019-08-30 14:17
閱讀 2734·2019-08-30 11:01
閱讀 2295·2019-08-29 17:19
閱讀 3653·2019-08-29 13:17