前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Druid源码阅读7-DruidDataSource的recycle过程

Druid源码阅读7-DruidDataSource的recycle过程

作者头像
冬天里的懒猫
发布2021-11-18 15:06:24
8650
发布2021-11-18 15:06:24
举报
文章被收录于专栏:做不甩锅的后端

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

1.回收过程

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

代码语言:javascript
复制
if (logDifferentThread //
    && (!isAsyncCloseConnectionEnable()) //
    && pooledConnection.ownerThread != Thread.currentThread()//
) {
    LOG.warn("get/close not same thread");
}

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

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

代码语言:javascript
复制
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切不是只读的连接,在回收的时候,先进行回滚操作。

代码语言:javascript
复制
// check need to rollback?
if ((!isAutoCommit) && (!isReadOnly)) {
    pooledConnection.rollback();
}

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

代码语言:javascript
复制
// 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方法:

代码语言:javascript
复制
//清空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都关闭,并将相关的报警灯内容清空。

关闭连接处理:

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

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

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

代码语言:javascript
复制
if (physicalConnection.isClosed()) {
        lock.lock();
        try {
            if (holder.active) {
                activeCount--;
                holder.active = false;
            }
            closeCount++;
        } finally {
            lock.unlock();
        }
        return;
    }

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

代码语言:javascript
复制
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,那么回收的时候需要对连接进行超时检测:

代码语言:javascript
复制
if (phyTimeoutMillis > 0) {
    long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
    if (phyConnectTimeMillis > phyTimeoutMillis) {
        discardConnection(holder);
        return;
    }
}

最关键的代码:

代码语言:javascript
复制
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();这样消费者线程就会产生调用。

2.recycle的调用时机

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

代码语言:javascript
复制
  public void close() throws SQLException ;
  
  public void syncClose() throws SQLException ;

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

代码语言:javascript
复制
if (filters.size() > 0) {
    FilterChainImpl filterChain = new FilterChainImpl(dataSource);
    filterChain.dataSource_recycle(this);
} else {
    recycle();
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/11/16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.回收过程
  • 2.recycle的调用时机
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档