问题描述

近期项目需要从虚拟机环境迁移到容器环境,其中有一个项目在迁移到容器环境之后的两天之内出现了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 {//共享的两个ArrayDescriptorprivate 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;

驱动11.2.0.4.0版本

首先查看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源码;

驱动10.1.0.5.0版本

同样分析此驱动版本中的相同类,同上首先查看OraclePreparedStatement中调用的setArray,最终会调用如下方法:

同样使用了connection作为对象锁,再看OracleTypeADT,相关代码如下:

可以看到这里并没有使用connection作为锁,而是使用了内置锁,所以就不会出现死锁问题;

问题总结

首先就是在迁移环境时一定要保证相关的依赖公共jar保证版本的一致,就算是低版本,高版本也不一样保证向下兼容;其次也是最重要的写业务逻辑时遇到公共变量时一定要谨慎,是否会出现多线程问题;

转载于:https://juejin.im/post/5cc01488f265da039f0f1375

记一次升级Oracle驱动引发的死锁相关推荐

  1. hibernate oracle驱动,出错场景是升级oracle驱动,将版本从ojdbc14升级到ojdbc6,hibernate执行原生态sql语句会报如下错误...

    出错场景是升级oracle驱动,将版本从ojdbc14升级到ojdbc6,hibernate执行原生态sql语句会报如下错误: org.hibernate.MappingException: No D ...

  2. 升级jenkins 导致jenkins启动失败_害你加班的Bug是我写的,记一次升级Jenkins插件引发的加班

    本文主要记录了下Jenkins升级插件过程中出现的场景,一次加班经历,事发时没有截图,有兴趣可以看看. 起因 需求 最近有个需求:在Jenkins流水线中完成下载Git上的文件简单修改并提交的功能 起 ...

  3. 害你加班的bug就是我写的,记一次升级Jenkins插件引发的加班

    主旨 本文主要记录了下Jenkins升级插件过程中出现的场景,一次加班经历,事发时没有截图,有兴趣可以看看. 起因 需求 最近有个需求:在Jenkins流水线中完成下载Git上的文件简单修改并提交的功 ...

  4. 终于编译好了qt的oracle驱动QOCI,连接成功!!!

    这么多天以来终于有件事可以小舒畅了一下了. 今天终于编译好了qt的oracle驱动QOCI,连接成功!!! Qt Commercial版只自带了SQLLite和ODBC的驱动,oracle的驱动要自己 ...

  5. Maven-Maven中添加Oracle驱动包到本地仓库

    问题 解决办法 1 确认本机安装了Maven 2下载对应版本的数据库驱动 官方下载jar 通过本地安装目录查找 oracle的驱动包说明 3安装驱动到本地仓库 pomxml 中使用Oracle驱动包 ...

  6. 【Qt】Qt5.12版本编译Oracle驱动教程

    00. 目录 文章目录 00. 目录 01. Qt5.12安装 02. Qt安装注意事项 03. Qt版本和Oracle安装路径说明 04. Qt5.12编译Oracle驱动(使用MinGW 64位) ...

  7. Maven添加Oracle驱动及依赖

    oracle驱动先去官网下载,下载下来后,需要安装到maven本地仓库,然后再pom中添加依赖. 1下载oracle驱动包 ojdbc6-11.2.0.3.jar 2命令行安装到maven仓库 mvn ...

  8. 如何在maven工程中加载oracle驱动

    2019独角兽企业重金招聘Python工程师标准>>> 由于oracle商业版权问题,maven不能通过中心资源库直接下载jar包,如果想要使用jar包,需要手动处理. 第一步:将o ...

  9. maven中引入oracle驱动报错Missing artifact com.oracle:ojdbc14:jar:10.2.0.4.0

    maven中引入oracle驱动报错Missing artifact com.oracle:ojdbc14:jar:10.2.0.4.0 问题:引入依赖之后会报错.在maven中央库中查找ojdbc, ...

最新文章

  1. NLP 实战:手把手带你搞定文本情感分析
  2. spell_picture第三版终于摆脱了命令行的操作
  3. Python “with” keyword
  4. MyClass a,b[2],*p[2]调用了几次构造函数
  5. mysql批量条件字段_mysql批量更新多条记录的同一个字段为不同值的方法
  6. 【基础】ORACLE中on commit preserve rows和 on commit delete rows的区别
  7. AGG第四十四课 渲染问题:绘制较宽轮廓和尖锐边缘
  8. AndroidStudio_android开发在线文档_在线API_蓝牙开发在线文档---Android原生开发工作笔记243
  9. 深度学习-吴恩达-笔记-5-深度学习的实践层面
  10. 坐火车卧铺,到底是上、中、下哪个好?其实简单对比一下就知道了
  11. Java常用开发工具推荐
  12. vscode SVN not found
  13. Microsoft Office2003sp2_5in1 迷你第7版(最终完美版)
  14. 我的web前端工作日记2------web前端工作的一天
  15. Niushop开源商城:618电商大趴开始预热啦!你的开源商城系统呢?
  16. 计算机毕业设计ssm青岛恒星科技学院机房管理系统0k0u9系统+程序+源码+lw+远程部署
  17. 解析解与数值解的区别
  18. _nop_();的由来和作用
  19. 今年今日==我的生日
  20. 消息队列简介-原理和应用

热门文章

  1. 密码学+赛博朋克,关于密码朋克们的奇妙故事
  2. 老铁 666!快手上市暴涨 200%,超 4000 员工成为千万富翁
  3. 为了提升续航,马斯克又引发一场“造芯”革命,华为比亚迪已进场
  4. 使用this.$router.push('')的方法进行路由跳转,提示'$router' of undefined问题
  5. C++ 退出双层for循环,解决 break、return、continue无法实现问题
  6. 迈向智慧化 物联网规模应用不断拓展
  7. django_2.0_请求处理
  8. 用Vue撸一个『A-Z字母滑动检索菜单』
  9. 镜像save保存和镜像重命名tag
  10. Pthon JSON