Druid源码解析(七):DruidDataSource的recycle过程

  Druid中的Connection在使用之后,要进行回收,而回收连接的方法就是recucle方法。 回收的主要目的是将连接的状态清空/重置之后,放置到连接池的connections数组的尾部,然后发送连接池lock的notEmpty条件变量通知消息,让等待的消费者线程来获取连接。

一、回收过程

  回收方法首先要做的是,判断回收线程是否为同一个线程。如果不是,则打印日志输出。

if (logDifferentThread //
    && (!isAsyncCloseConnectionEnable()) //
    && pooledConnection.ownerThread != Thread.currentThread()//
) {
    LOG.warn("get/close not same thread");
}

 

  这个日志级别是warn,这是我们在使用连接池的过程中特别要注意的地方,一般来说,只有连接池发生了连接泄露,使用连接的线程长期持有连接而不执行具体操作,连接泄漏监测的线程才会来关闭连接。这种情况需要特别注意。 此外,连接泄露监测机制的removeAbandoned机制也会调用recycle方法来进行回收。

pooledConnection.traceEnable与activeConnections都是与removeAbandoned机制相关的参数,这将在后续关于removeAbandoned机制的时候详细分析。

if (pooledConnection.traceEnable) {
    Object oldInfo = null;
    activeConnectionLock.lock();
    try {
        if (pooledConnection.traceEnable) {
            //将连接从activeConnections中移除 考虑到多线程场景,要加锁
            oldInfo = activeConnections.remove(pooledConnection);
            pooledConnection.traceEnable = false;
        }
    } finally {
        activeConnectionLock.unlock();
    }
    if (oldInfo == null) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
        }
    }
}

  rollback处理:如果不是自动commit切不是只读的连接,在回收的时候,先进行回滚操作。  

// check need to rollback?
if ((!isAutoCommit) && (!isReadOnly)) {
    pooledConnection.rollback();
}

 

  reset处理:此处需要判断是否为多线程场景,如果不是同一个线程,因为连接本身的线程有可能调用完毕之后释放的时候也会调用recycle,因此这个地方需要考虑到多线程。

// reset holder, restore default settings, clear warnings
boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
if (!isSameThread) {
    final ReentrantLock lock = pooledConnection.lock;
    lock.lock();
    try {
        holder.reset();
    } finally {
        lock.unlock();
    }
} else {
    holder.reset();
}

 

  最关键的部分是reset方法:

//清空Listeners
connectionEventListeners.clear();
statementEventListeners.clear();

lock.lock();
try {
    for (Object item : statementTrace.toArray()) {
        Statement stmt = (Statement) item;
        //关闭statement
        JdbcUtils.close(stmt);
    }
    //清空statementTrace
    statementTrace.clear();
} finally {
    lock.unlock();
}
//清空warning信息
conn.clearWarnings();

 

  reset方法将connection中的statement都关闭,并将相关的报警灯内容清空。

  关闭连接处理:

//如果状态为discard 则直接退出
if (holder.discard) {
    return;
}
//如果超过连接最大的使用次数,那么也将关闭连接
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
    discardConnection(holder);
    return;
}

  这个地方如果状态为udiscard,则直接退出recycle方法。会在后续的方法中被discard。 如果达到最大调用次数,也会关闭连接。

  关闭清理: 如果连接已经关闭,则加锁,减去计数器即可。

if (physicalConnection.isClosed()) {
        lock.lock();
        try {
            if (holder.active) {
                activeCount--;
                holder.active = false;
            }
            closeCount++;
        } finally {
            lock.unlock();
        }
        return;
    }

  

  testOnReturn处理: 如果开启了testOnReturn,则发送测试数据,如果测试失败,则关闭连接。

if (testOnReturn) {
    boolean validate = testConnectionInternal(holder, physicalConnection);
    if (!validate) {
        JdbcUtils.close(physicalConnection);

        destroyCountUpdater.incrementAndGet(this);

        lock.lock();
        try {
            if (holder.active) {
                activeCount--;
                holder.active = false;
            }
            closeCount++;
        } finally {
            lock.unlock();
        }
        return;
    }
}

 

  如果配置了druid.phyTimeoutMillis,那么回收的时候需要对连接进行超时检测。

if (phyTimeoutMillis > 0) {
    long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
    if (phyConnectTimeMillis > phyTimeoutMillis) {
        discardConnection(holder);
        return;
    }
}

  最关键的代码:

lock.lock();
try {
    //修改active状态和activeCount计数器
    if (holder.active) {
        activeCount--;
        holder.active = false;
    }
    //增加closeCount计数器
    closeCount++;
    //将连接放置到数组的末尾
    result = putLast(holder, currentTimeMillis);
    recycleCount++;
} finally {
    lock.unlock();
}

 

  这是连接回收的核心代码,就是将连接放置到数组的末尾。 在putLast方法中会调用 notEmpty.signal();这样消费者线程就会产生调用。

二、recycle调用时机

  recycle方法将在连接的close方法中被调用。在需要关闭连接的时候,调用recycle,将符合规则的连接添加到连接池的末尾。 调用recycle的方法:

public void close() throws SQLException ;
  
public void syncClose() throws SQLException ;

  close和syncClose方法都会调用recycle。 在关闭连接之前,跟获取连接一样,都要进行责任链模式的filter处理。

if (filters.size() > 0) {
    FilterChainImpl filterChain = new FilterChainImpl(dataSource);
    filterChain.dataSource_recycle(this);
} else {
    recycle();
}

 

  这个过程与getConnection方法执行filter的过程类似。

 

posted @ 2022-05-17 23:08  李聪龙  阅读(776)  评论(0编辑  收藏  举报