一个JDBC驱动注册死锁问题总结

群里有个大神(你假笨)再讲解工作中碰到的一个死锁问题.

这个是大神后来总结的文章:http://lovestblog.cn/blog/2014/07/08/jdk-sql-deadlock/

情况是这样的:

项目碰到多线程初始化JDBC驱动时,产生死锁,如下实例所示: (我的环境: JDK1.7.0_45, msql_jdbc:mysql-connector-java-5.1.29)

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代码块上,

下面是发生死锁时卡死的大概代码位置

线程A:静态代码块主动注册驱动

线程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

时间: 2024-10-02 19:54:36

一个JDBC驱动注册死锁问题总结的相关文章

JDBC驱动自身问题引发的FullGC

公众号HelloJava刊出一篇<MySQL Statement cancellation timer 故障排查分享>,作者的某服务的线上机器报 502(502是 nginx 做后端健康检查时不能连接到 server 时抛出的提示),他用 jstack -l 打印线程堆栈,发现了大量可疑的"MySQL Statementcancellation timer",进一步探究原因,原来是业务应用将数据库更新操作和云存储传图操作放在同一个事务.当云存储发生异常时,由于缺少云存储操作

jdbc-用eclipse做一个简单的注册模块,数据传不到数据库中

问题描述 用eclipse做一个简单的注册模块,数据传不到数据库中 用eclipse做一个简单的注册模块,输入用户名和密码,选择性别,按提交,将数据写如数据库,运行时出现的错误: HTTP Status 500 - javax.servlet.ServletException: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'UserName' cannot be null 有

《Android深度探索(卷1):HAL与驱动开发》——6.3节第一个Linux驱动:统计单词个数

6.3 第一个Linux驱动:统计单词个数Android深度探索(卷1):HAL与驱动开发源程序目录:<光盘根目录>/sources/word_count本节将给出我们的第1个Linux驱动的例子.这个驱动程序并没有访问硬件,而是利用设备文件作为介质与应用程序进行交互.应用程序通过向设备文件传递一个由空格分隔的字符串(每一个被空格隔开的子字符串称为一个单词),然后从设备文件读出来的是该字符串包含的单词数.本例的驱动程序使用C语言实现,源代码文件路径如下. 6.3.1 编写Linux驱动程序前的

一个jdbc连接oracle8的例子(本机的页可以)!!!!

        try    {            //加载一个Oracle驱动            DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());            //使用OCI8连接到数据库            conn=DriverManager.getConnection("jdbc:oracle:oci8:@DatabaseName"+,"user",&

求大神,如何在实现一个服务器端的注册和登陆验证?

问题描述 求大神,如何在实现一个服务器端的注册和登陆验证? 想做一个服务器端,一个客户端,客户端发送信息给服务器验证,服务器端要怎么实现?大神给个方向呗,非要租用服务器吗?听说野狗api也可以? 解决方案 用户是你一个系统最重要的资产.连一个用户信息都没有的系统想不出有什么搞的必要. 解决方案二: 是做短信验证码?我们用了的阿里大鱼的短信验证平台. 解决方案三: 就是注册和登陆,我要数据库在服务器端,然后客户端发送用户名和密码给服务器验证或插入数据库,不租用服务器有什么平台可以实现吗?

先做点好事,转点东东来,用PHP和MySQL构建一个数据库驱动的网站(-)

mysql|数据|数据库 摘要 在这篇文章中,我们会着手解决在构建一个数据库驱动的网站的过程中将会遇到的问题.而我们只会使用两个新的工具,PHP和MySQL.如果你的Web主机支持PHP/MySQL,那么你会省掉不少麻烦.如果不是这样,你也不用提心,我们也会学习如何在Unix和Windows下安装相应程序. 这篇文章是提供给那些有可能学会服务器端程序开发的中高级的网页设计者的.我们会认为我们的读者熟悉HTML,所以我们在使用HTML时不会给出什么解释.另外,在有些地方我们可能还会用到少量的Jav

JSP连接各类数据库大全SQLServer2000 JDBC驱动的完整安装及测试?

js|server|sqlserver|数据|数据库 JSP连接各类数据库大全SQLServer2000 JDBC驱动的完整安装及测试说明 SQLServer2000 JDBC驱动的完整安装及测试说明(转载) 一.下载SQLSERVER2000的jdbc驱动程序.在微软站点就有这个驱动程序:Window操作系统http://www.uncj.com/upload/files/ms_jdbc_setup.exe http://download.microsoft.com/download/SQLS

用PHP和MySQL构建一个数据库驱动的网站(六)

mysql|数据|数据库 摘要 在这一章内我们会学习到如何在一个Web页面中向数据库中存储信息并显示它. (2002-08-29 14:11:25) --------------------------------------------------------------------------------By Wing, 出处:Linuxaid 第四章: 用PHP访问MySQL数据库 在这一章内我们会学习到如何在一个Web页面中向数据库中存储信息并显示它.之前我们已经安装了MySQL这个关系

热门数据库JDBC驱动试用心得

一.引言 无论是初级还是中高级技术人员,面对着各式各样的数据库平台层出不穷和众多的操作系统功能不断升级,难免会眼花缭乱.特别是当系统面临升级,无论操作平台还是数据库平台,甚至架构都可能需要更替的时候,如何才能抵住众说纷纭,把握好你的选择.幸运的是,利用Java技术可以将这些不同种别的数据库平台和操作系统无缝地连接起来,真正地做到"集百家之长而为我所用". 本文将通过一组真实的案例来向读者介绍如何做到简单地使用JDBC驱动来实现在不同的操作系统下存取几款较为热门的数据库平台. 特别是对J