好的电商网站建设与维护意味着什么,网站用哪个做,导航网址大全,武极云主机轻车熟路#xff0c;连接归还是通过Connection的代理对象重写close方法完成的,通过前面的学习我们已经知道Connectin的代理对象是DruidPooledConnection#xff0c;所以我们直接看DruidPooledConnection的close方法。
DruidPooledConnection#close
直接上代码#xff1a; …轻车熟路连接归还是通过Connection的代理对象重写close方法完成的,通过前面的学习我们已经知道Connectin的代理对象是DruidPooledConnection所以我们直接看DruidPooledConnection的close方法。
DruidPooledConnection#close
直接上代码 public void close() throws SQLException {if (this.disable) {return;}DruidConnectionHolder holder this.holder;if (holder null) {if (dupCloseLogEnable) {LOG.error(dup close);}return;}DruidAbstractDataSource dataSource holder.getDataSource();boolean isSameThread this.getOwnerThread() Thread.currentThread();if (!isSameThread) {dataSource.setAsyncCloseConnectionEnable(true);}if (dataSource.isAsyncCloseConnectionEnable()) {syncClose();return;}判断当前ConnectionDruidPooledConnection的状态为disbale、或者connectionHolderDruidConnectionHolder为null说明连接已关闭了则直接返回。
然后判断连接的ownerThread获取connection的线程与当前线程不是同一线程的话则设置异步关闭asyncCloseConnectionEnabletrue。调用syncClose()方法。
syncClose()方法和同线程关闭方式的代码逻辑基本一致只不过syncClose()整个方法需要加锁同线程关闭则不需要。
所以我们就不贴出syncClose()方法的源码了。
在什么场景下数据库连接会跨线程关闭一个线程获取到数据库连接然后会交给另外一个线程由另外一个线程执行连接关闭应用层这么做是为了实现多线程之间的事务处理
接下来 if (!CLOSING_UPDATER.compareAndSet(this, 0, 1)) {return;}try {for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {listener.connectionClosed(new ConnectionEvent(this));}ListFilter filters dataSource.getProxyFilters();if (filters.size() 0) {FilterChainImpl filterChain new FilterChainImpl(dataSource);filterChain.dataSource_recycle(this);} else {recycle();}} finally {CLOSING_UPDATER.set(this, 0);}this.disable true;}CAS方式修改当前对象的closing属性值为10-1如果修改不成功则说明有其他线程正在试图关闭当前连接直接返回。
获取ConnectionHolder的所有ConnectionEventListener对象给所有监听对象发送连接关闭通知。
然后获取dataSource的ProxyFilters不空的话调用filterChain的dataSource_recycle方法有关dataSource过滤器我们依然暂时不管。没有filter的话调用recycle()回收连接之后CAS方法修改closing属性值为0并设置当前连接对象的disabletrue。
所以我们发现连接归还应该是recycle()方法中实现的。
DruidPooledConnection#recycle()
连接DruidPooledConnection的recycle方法 public void recycle() throws SQLException {if (this.disable) {return;}DruidConnectionHolder holder this.holder;if (holder null) {if (dupCloseLogEnable) {LOG.error(dup close);}return;}if (!this.abandoned) {DruidAbstractDataSource dataSource holder.getDataSource();dataSource.recycle(this);}this.holder null;conn null;transactionInfo null;closed true;}如果当前连接没有被遗弃abandoned的话调用dataSource的recycle方法回收之后清理相关对象、设置close为true。
abandoned是在removeAbandoned参数打开、连接执行时长超过设定的超时时长removeAbandonedTimeoutMillis之后设置的。有关removeAbandoned我们后面会专门进行分析此处略过。
那接下来就是DataSource的recycle方法了。
DruidDataSrouce#recycle
代码比较长我们还是分段分析
/*** 回收连接*/protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {final DruidConnectionHolder holder pooledConnection.holder;if (holder null) {LOG.warn(connectionHolder is null);return;}if (logDifferentThread // (!isAsyncCloseConnectionEnable()) // pooledConnection.ownerThread ! Thread.currentThread()//) {LOG.warn(get/close not same thread);}final Connection physicalConnection holder.conn;if (pooledConnection.traceEnable) {Object oldInfo null;activeConnectionLock.lock();try {if (pooledConnection.traceEnable) {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());}}}
吐槽一句终于出现了哪怕是一句话的javaDoc说明回收连接。整个Druid连接池的源码中都非常吝啬少有注释。
判断当前连接DruidPooledConnection的traceEnable属性为true的话加activeConnectionLock锁之后将当前连接从activeConnections中移除。
traceEnable属性其实反应的还是removeAbandoned参数这个参数虽然在正式环境不建议打开但是代码中阴魂不散到处都有。removeAbandoned参数打开的情况下获取连接的时候会将连接放入activeConnections中并同时设置连接的traceEnable为true。所以我们其实可以认为这个traceEnable其实就是removeAbandoned参数换了个名字而已。
所以这里的逻辑是removeAbandoned参数打开的话连接归还的时候如果该连接没有被abandoned的话在归还连接时会将当前连接从activeConnections中移除该连接就会避免被abandoned处理。
接下来 final boolean isAutoCommit holder.underlyingAutoCommit;final boolean isReadOnly holder.underlyingReadOnly;final boolean testOnReturn this.testOnReturn;try {// check need to rollback?if ((!isAutoCommit) (!isReadOnly)) {pooledConnection.rollback();} boolean isSameThread pooledConnection.ownerThread Thread.currentThread();if (!isSameThread) {final ReentrantLock lock pooledConnection.lock;lock.lock();try {holder.reset();} finally {lock.unlock();}} else {holder.reset();}if (holder.discard) {return;}if (phyMaxUseCount 0 holder.useCount phyMaxUseCount) {discardConnection(holder);return;}if (physicalConnection.isClosed()) {lock.lock();try {if (holder.active) {activeCount--;holder.active false;}closeCount;} finally {lock.unlock();}return;}
对没有提交的事务做回滚处理。之后调用DruidConnectionHolder的reset方法重新设置连接属性为默认值因为连接获取之后应用从根据需要可能对连接属性重新进行了设置归还的时候重新设置会默认值以便每一次应用获取连接之后都能拿到各项属性设置为默认值的连接、而不是不确定。
这里对没有提交的事务的判断条件是!isAutoCommit获取的是物理连接Connection的isAutocommit属性。我们知道如果应用开启事务管理的话获取连接之后会设置连接的autoCommit为false事务提交或回滚之后一般情况下应用也会恢复连接的默认设置这个时候autoCommit就会被恢复为true。比如Spring的事务框架在事务提交之后会通过TransactionManager的cleanupAfterCompletion-doCleanupAfterCompletion设置autoCommit为true。所以正常来讲应用已经提交事务之后autoCommit就会变为true也就不需要Druid在归还连接的时候处理回滚了。
Druid增加这个判断可能是为了给应用擦屁股猜测而已应用层没有启用事务框架的情况下手动开启了事务、设置autoCommit为false执行完成之后没有重置autoCommit这个时候应用可能commit、也可能没有commit事务Durid的处理方式是一律回滚。
仔细检查了HikariPool的连接回收过程并没有这个回滚处理。个人不赞成基于猜测而已………Druid的这个回滚处理因为事务提交还是回滚终究还是应用应该关注的事情应用层为了简化处理逻辑可以把事务管理交给框架比如Spring事务框架来处理。也就是说要么是应用层自己解决、要么交给事务框架解决事务的提交或回滚是比较合理的选择。
继续。
检查如果DruidConnectionHolder已经被弃用连接已经被弃用直接返回不归还。
检查如果物理连接已经被关闭则加锁修改当前holder的active状态为false直接返回不归还。
然后 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;}}if (holder.initSchema ! null) {holder.conn.setSchema(holder.initSchema);holder.initSchema null;}if (!enable) {discardConnection(holder);return;}检查testOnReturn参数如果设置为true则测试连接可用性如果连接不可用处理方式同上修改holder的activefalse直接返回不归还。
testOnReturn参数也没有必要设置道理和testOnBorrow参数一样会影响性能。这两个参数的默认设置都是false不打开。
之后检查当前dataSrouce如果不可用则调用discardConnection关闭连接不归还。
然后 boolean result;final long currentTimeMillis System.currentTimeMillis();if (phyTimeoutMillis 0) {long phyConnectTimeMillis currentTimeMillis - holder.connectTimeMillis;if (phyConnectTimeMillis phyTimeoutMillis) {discardConnection(holder);return;}}lock.lock();try {if (holder.active) {activeCount--;holder.active false;}closeCount;result putLast(holder, currentTimeMillis);recycleCount;} finally {lock.unlock();}if (!result) {JdbcUtils.close(holder.conn);LOG.info(connection recyle failed.);}} catch (Throwable e) {holder.clearStatementCache();if (!holder.discard) {discardConnection(holder);holder.discard true;}LOG.error(recyle error, e);recycleErrorCountUpdater.incrementAndGet(this);}}如果设置了phyTimeoutMillis默认设置为-1不检查的话检查当前连接创建以来的时长是否超过了该参数的设置超过的话关闭连接不归还。这个参数也不建议设置。
之后加锁调用putLast(holder, currentTimeMillis);归还连接如果归还失败则调用JdbcUtil.close方法底层直接关闭连接。
putLast是归还连接的核心方法。
DruidDataSource#putLast
putLast是连接归还的最后一步目的是各种检查校验、连接属性重置之后最终将连接放回到连接池connections中。
putLast是在锁状态下执行的。
看代码 boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {if (poolingCount maxActive || e.discard || this.closed) {return false;}e.lastActiveTimeMillis lastActiveTimeMillis;connections[poolingCount] e;incrementPoolingCount();if (poolingCount poolingPeak) {poolingPeak poolingCount;poolingPeakTime lastActiveTimeMillis;}notEmpty.signal();notEmptySignalCount;return true;}检查如果没必要归还的话比如连接池数量大于参数设定的maxActive数量、或者当前连接已经被遗弃、或者当前dataSource已经被关闭则不需要归还直接返回。
将连接放入到connections的尾部之后增加连接池数量poolingCount。
调用notEmpty.signal();唤醒等待获取连接的线程连接池中有新的连接加入可以获取了。
Druid关闭连接归还连接代码分析完毕
小结
Druid连接池的初始化、连接获取以及连接归还的源码分析完毕后面会补充一篇文章分析连接遗弃的处理。
Thanks a lot!
上一篇 连接池 Druid 三 - 获取连接 getConnection 下一篇 连接池 Druid 补充 - removeAbandoned