池化连接为什么要加上安全关闭(BeeCP)

原创
2021/05/05 15:51
阅读数 438

去年个人撰写过一篇博客(想知道连接不安全关闭会造成什么影响吗? https://my.oschina.net/u/3918073/blog/4309470)曾有网在阅读过文章后,发出质疑(热烈欢迎质疑,有的进步,就是从怀疑开始的). 自从光连接池爆红后,感觉它快成了连接池领域的标准了。我今天有一句非常重要的话告诉大家:池化的连接的安全关闭非常重要,如不加上,也许就是一个潜在性的炸弹。(^-^ 呵呵,话听起来有点严重啊),下面我们一步一步来探讨这个问题。

一:池化连接与关闭

使用过Java数据源的朋友,应该大多知道数据源背后通常有一个连接池的存在,从池中返回的通常只是物理连接的包装对象而已(我通常把池化连接比作为电视机,包装对象比作遥控器),那么如何实现一个包装类呢? 下面我写一个供参考的例子

public class ProxyConnection implements Connection {
   private boolean isClosed;//是否标记为关闭
   private Connection rawConn;//物理连接对象
   private Pool connectionPool;//连接池对象
 
   public ProxyConnection(Connection rawConn,Pool connectionPool){
      this.rawConn=rawConn;
      this.connectionPool=connectionPool;
   }

   //。。。。。。。。。。。。。。。。。。。。。。。//其他实现方法省略掉

   public void close()throws SQLException{//关闭方法,把物理连接归还到池中去
      if(isClosed){
            return;//已经关闭不做处理
      }else{
         isClosed=true;//标记为已经关闭
         connectionPool.recycle(rawConn);//(关键处)是的,此处就是归还啦
      }
   }
}

也许大家看后,也许发出声音:哇,连接池实现好简单啊。是的,连接池确实不难的。针对上述代码我们来假设一个场景:用户利用A线程获取到连接,然后将连接对象分发到B,C,D,E等更多线程中去使用(是的,一般不允许这样干,但是我们没有办法约束那些不守规矩的用户),若这个连接对象同时在A,B,C,D线程中触发关闭动作,那么会发生什么呢? 由于上述代码未加上同步安全控制,那么A,B,C,D在并发的情况,是有可能同时到达关键处吗?答案是肯定的,那么意味着同一个物理对象是要释放多次的,这个释放的背后到底会发生什么呢? 若池中有等待者,这个释放只推送给其中一个等待者,但是由于上述代码未做安全控制,多线程同时到达会发生什么呢? 所以上述代码需要调整为:

public class ProxyConnection implements Connection {
   private boolean isClosed;//是否标记为关闭
   private Connection rawConn;//物理连接对象
   private Pool connectionPool;//连接池对象
 
   public ProxyConnection(Connection rawConn,Pool connectionPool){
      this.rawConn=rawConn;
      this.connectionPool=connectionPool;
   }

   //。。。。。。。。。。。。。。。。。。。。。。。//其他实现方法省略掉

   public synchronized void close()throws SQLException{//关闭方法,把物理连接归还到池中去
      if(isClosed){
            return;//已经关闭不做处理
      }else{
         isClosed=true;//标记为已经关闭
         connectionPool.recycle(rawConn);//(关键处)是的,此处就是归还啦
      }
   }
}

二:光连接池如何实现关闭

https://github.com/brettwooldridge/HikariCP/blob/dev/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java(247行)把这个方法浓缩一下,剔除无关性代码,可用下图表示:

 三:问题分析与模拟

光连接池这样的实现有问题吗? 我不知道!如果,我说的是如果,只要并发线程同时到达if处,就会存在安全性问题,因此仿造一段类似效果的代码

1:模拟连接对象

//此类用于模拟一个连接对象
class MockConnection {
    private static final Object CLOSED_CONNECTION = new Object();
    private Object delegate = new Object();//模拟原生对象
    private synchronized void clearStatement() {
        //此处模拟一个同步效果的清除方法,为了测试此处放空代码
    }
    public void close() {
        clearStatement();//模拟方法
        if (delegate != CLOSED_CONNECTION) {//多个并发线程,只要同时到达,那finally均会执行
            try {
                /********************************************
                 *
                 *         省略掉中间
                 *
                 ********************************************/
            } finally {
                delegate = CLOSED_CONNECTION;//偷梁换柱
                System.out.println("到达回收处");
            }
        }
    }
}

提醒:多线程并发模拟关闭,只要屏幕出现多于一个 “到达回收处”,这表示存在安全关闭问题。

2:模拟并发线程

//模拟并发线程
class MockThread extends Thread {
    private long closeTimeNanos;//并发执行时刻点
    private MockConnection con;

    public MockThread(MockConnection con, long closeTimeNanos) {
        this.con = con;
        this.closeTimeNanos = closeTimeNanos;
    }

    public void run() {
        LockSupport.parkNanos(closeTimeNanos - System.nanoTime());//纳秒级暂停
        con.close();//执行关闭
    }

    public static void main(String[] args) {
        int threadSize = 10;//模拟10个并发线程去关做关闭动作
        MockConnection con = new MockConnection();
        long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);//并发执行时刻点
        for (int i = 0; i < threadSize; i++) {
            new MockThread(con, deadline).start();
        }
    }
}

3:模拟后的测试截图

附带贴上小蜜蜂池的实现代码

//call by borrower,then return PooledConnection to pool
public final void close() throws SQLException {
    synchronized (this) {//safe close
        if (isClosed) return;
        isClosed = true;
        delegate = CLOSED_CON;
        if (pCon.openStmSize > 0) pCon.clearStatement(); 
    }
    pCon.recycleSelf();//回收连接

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部