为什么这种锁定更改对JDBC性能影响如此之大?

时间:2012-02-01 09:59:47

标签: java multithreading jdbc

我正在编写一个数据库连接池,用于大型多线程应用程序,写入目标jdk4标准,使用以下代码,我可以通过我的测试用例在0.4秒内通过LAN查询一个mysql数据库1000次。

synchronized(lockA) {
    if(free.size() > 0) {
        c = (Connection) free.removeFirst();
    }

    if(c == null) {
        c = DriverManager.getConnection(query, name, passw);
    }
}

这里lockA保护空闲列表(LinkedList),它用于修改和访问列表的位置。将getConnection移出此锁并进入其自己的受保护块是有意义的。 getConnection需要受到锁的保护,因为某处它不是线程安全的。

因此,如果我更改它,那么DriverManager和列表就会被单独的锁保护,就像这样。

synchronized(lockA) {
    if(free.size() > 0) {
        c = (Connection) free.removeFirst();
    }
}

if(c == null) {
    synchronized(lockB) {
        c = DriverManager.getConnection(query, name, passw);
    }
}

我得到连续缓存未命中(C为空),因此性能降低,因此需要4秒才能执行相同的查询,耗时0.4秒。

为什么会这样?

编辑:

我已经解决了这个问题,问题产生于当创建太多连接时函数阻塞的方式。

这是在函数开始时发生的事情。

synchronized(waitLocK) {
    try {
        while(count >= limit) {
            waitLock.wait();
        } 
    } catch (InterruptedException e) {
    }
}

释放连接时释放waitLock。但是这里发生的事情是,在创建连接的代码块之后,count变量(volatile)会递增。

这有打开门的效果,因为当1000个线程试图通过等待测试时,它们都通过了,因为count仍为0,然后重载了getConnection()。

将try ++移动到try之后解决了这个问题。

1 个答案:

答案 0 :(得分:0)

代码的第二种形式允许无限数量的线程同时请求与数据库的连接,其中第一种形式一次只允许一个线程请求新连接。

在Java中编写并发代码时,我有一个简单的规则:

如果你的代码中包含synchronized关键字,那么它可能是错误的,并且会以微妙和不可预测的方式失败。

开玩笑说,你需要仔细思考为什么你不能用标准的并发类来实现你想要的并发结果。

skaffman的评论是正确的 - 你真的不想实现连接池。选择任何一种可用的实现方式。

但是,假设这不是连接池。为什么使用synchronize + LinkedList而不是ConcurrentLinkedQueue?您的逻辑尝试从列表中获取连接;如果没有可用的连接,它会创建一个新的连接(可能是调用者在完成连接时会释放回列表)。

Java Concurrency In Practice是一本很棒的书,如果你还没有读过它。