jedis为了管理网络连接使用了apache commons-pool用于连接资源管理,我使用jedis版本是2.8.2,依赖的commons-pool 版本是2.4.2
之前我的项目中调用jedis时,都是添加ShutdownHook在程序结束时自动关闭JedisPool.就是类似下面的代码:

 static{// 程序退出时自动销毁连接池对象Runtime.getRuntime().addShutdownHook(new Thread(){@Overridepublic void run() {closeAll();}});}/*** 关闭并删除所有资源池中的{@link JedisPool}实例*/public synchronized static void closeAll(){for(Iterator<JedisPool> itor = POOL_SET.iterator();itor.hasNext();){JedisPool p = itor.next();itor.remove();p.close();}}

这样比较方便,就是应用层对JedisPool只管用就行了,可以不需要主动去执行关闭动作。

然而,最近在一个新项目中使用jedis时,我还是照以往的方式,没有去主动关闭JedisPool,让程序结束时自动关闭。却出了问题:程序没有正常关闭,如下图,可以看到除了守护线程外,有一个名为commons-pool-evictor-thread的线程还在运行,导致程序无法退出。

为什么会这样的?
对比我之前的程序,我发现了不一样的地方,如下图是能够正常关闭的一个测试程序的线程运行情况,可以看到有一个名为commons-pool-EvictionTimer的线程,但与上图不同的是,这个线程是守护线程。我们知道JVM不需要等守护线程结束就可以结束。所以这个commons-pool-EvictionTimer守护线程不会影响JVM关闭。

现在有两个问题:

  1. commons-pool-evictor-threadcommons-pool-EvictionTimer线程是做什么用的?
  2. 为什么在这两个程序中evictor线程的类型居然不一样?

带着这两个问题我开始分析jedis的源码,首先解决第一个问题.

commons-pool-evictor-thread线程

通过分析jedis和google搜索,大概搞明白commons-pool-evictor-thread线程的作用,这是commons-pool产生的线程,evictor的英文解释是“驱逐者”,所以在这里commons-pool-evictor-thread是一个定时执行的任务的线程,用于定期从资源池中删除空闲不用的资源对象。用在JedisPool中就是定期删除空闲的Jedis对象。

为什么commons-pool-evictor-thread线程的类型居然不一样?

下面是redis.clients.util.Pool类的代码片段,从代码中可以到jedis对commons-poolGenericObjectPool类的初始化

public abstract class Pool<T> implements Closeable {protected GenericObjectPool<T> internalPool;/*** Using this constructor means you have to set and initialize the internalPool yourself.*/public Pool() {}public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {initPool(poolConfig, factory);}
//  ......// 初始化资源池public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {if (this.internalPool != null) {try {closeInternalPool();} catch (Exception e) {}}this.internalPool = new GenericObjectPool<T>(factory, poolConfig);}
// .....
}

在commons-pool 2.4.2版本的EvictionTimer.java代码中可以找到创建commons-pool-EvictionTimer线程的代码

    /*** {@link PrivilegedAction} used to create a new Timer. Creating the timer* with a privileged action means the associated Thread does not inherit the* current access control context. In a container environment, inheriting* the current access control context is likely to result in retaining a* reference to the thread context class loader which would be a memory* leak.*/private static class PrivilegedNewEvictionTimer implements PrivilegedAction<Timer> {/*** {@inheritDoc}*/@Overridepublic Timer run() {// 创建定时器线程,isDaemon为true,指定为守护线程return new Timer("commons-pool-EvictionTimer", true);}}

所以我们可以理解使用commons-pool 2.4.2版本,commons-pool-EvictionTimer线程因为是守护线程所以不影响JVM关闭。
但两个项目中Eviction的名字不一样(commons-pool-EvictionTimer vs commons-pool-evictor-thread),让我意识到,我可能使用了不同的commons-pool版本。检查maven的dependency hierarchy果然发现了问题,如下红线标出的因为与项目中其他模块依赖commons-pool的版本号冲突,所以这里commons-pool的版本自动升级到2.5.0:

在2.5.0的EvictionTimer.java代码中创建evicition线程的代码是这样的:

    /*** Thread factory that creates a thread, with the context classloader from this class.*/private static class EvictorThreadFactory implements ThreadFactory {@Overridepublic Thread newThread(final Runnable r) {// 创建commons-pool-evictor-thread线程,但没有指定为守护线程final Thread t = new Thread(null, r, "commons-pool-evictor-thread");AccessController.doPrivileged(new PrivilegedAction<Void>() {@Overridepublic Void run() {t.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());return null;}});return t;}}

上面的代码中创建commons-pool-evictor-thread线程,但没有指定为守护线程所以这个线程不会自动结束导致程序不能正常退出.

解决方案

好了,到这里问题的原因总算是找到了。那么 怎么解决commons-pool-evictor-thread线程导致的程序不能结束的问题呢?

使用commons-pool 2.4.2版本

想办法让commons-pool的版本号退回到2.4.2这个版本,不要使用高于此版本的commons-pool.因为目前所有高于此版本的common2-pool版本创建的commons-pool-evictor-thread线程都不是守护线程。

显式执行JedisPool#close()方法

造成commons-pool-evictor-thread线程没有被关闭的原因就是没有执行GenericObjectPool#close()方法,
所以在程序结束时显式执行JedisPool#close()方法,执行JedisPool#close()方法会自动关闭内部使用的GenericObjectPool实例。
下面是下面是redis.clients.util.Pool类的代码片段

public abstract class Pool<T> implements Closeable {// 内部的资源池对象protected GenericObjectPool<T> internalPool;/*** Using this constructor means you have to set and initialize the internalPool yourself.*/public Pool() {}@Overridepublic void close() {destroy();}public void destroy() {closeInternalPool();}protected void closeInternalPool() {try {// 调用GenericObjectPool#close()方法internalPool.close();} catch (Exception e) {throw new JedisException("Could not destroy the pool", e);}}
}

下面则是GenericObjectPool.java的close()方法实现

public class GenericObjectPool<T> extends BaseGenericObjectPool<T>implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {// ........./*** Closes the pool. Once the pool is closed, {@link #borrowObject()} will* fail with IllegalStateException, but {@link #returnObject(Object)} and* {@link #invalidateObject(Object)} will continue to work, with returned* objects destroyed on return.* <p>* Destroys idle instances in the pool by invoking {@link #clear()}.*/@Overridepublic void close() {if (isClosed()) {return;}synchronized (closeLock) {if (isClosed()) {return;}// 资源池关闭之前先停止Evictor线程startEvictor(-1L);closed = true;// This clear removes any idle objectsclear();jmxUnregister();// Release any threads that were waiting for an objectidleObjects.interuptTakeWaiters();}}
// .....
}

后记

evictor线程设置为守护线程显然更方便应用程序的设计,但为什么2.4.2以后的版本启动Evictor线程不再是守护线程?在我看来这应该是个bug。
在commons-pool上星期(2019/3/30)提的一次交中显示这个问题已经被修复了,参见pull request: https://github.com/apache/commons-pool/pull/20
下面是最新版本的EvictionTimer.java中创建commons-pool-evictor-thread线程的代码片段:

    /*** Thread factory that creates a daemon thread, with the context class loader from this class.*/private static class EvictorThreadFactory implements ThreadFactory {@Overridepublic Thread newThread(final Runnable runnable) {final Thread thread = new Thread(null, runnable, "commons-pool-evictor-thread");// commons-pool-evictor-thread设置为守护线程,这就是唯一的修改thread.setDaemon(true); // POOL-363 - Required for applications using Runtime.addShutdownHook(). --joshlandin 03.27.2019AccessController.doPrivileged(new PrivilegedAction<Void>() {@Overridepublic Void run() {thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());return null;}});return thread;}}

所以commons-pool下一个release版本就会包含这个PR,问题可以彻底解决。

jedis:commons-pool-evictor-thread线程不能自动关闭?相关推荐

  1. Apache Commons Pool 故事一则 专题

    Apache Commons Pool 故事一则 最近工作中遇到一个由于对commons-pool的使用不当而引发的问题,习得正确的使用姿势后,写下这个简单的故事,帮助理解Apache Commons ...

  2. Thread 线程基础之-线程相关知识

    线程的优先级 设置或者获得当前线程的优先级: using System;using System.Collections.Generic;using System.Text;using System. ...

  3. 【日常Exception】第二十四回:nested exception is java.lang.NoClassDefFoundError: redis/clients/jedis/util/Pool

    热门系列: 程序人生,精彩抢先看 日常异常,是否也有你似曾相识的那一个 1.问题 近期遇到的一个异常问题如题所示,下面是完整的异常内容,原景重现: PropertyAccessException 1: ...

  4. 在Android中使用Handler和Thread线程执行后台操作

    在 Android中使用Handler和Thread线程执行后台操作 对于线程的控制,我们将介绍一个 Handler类,使用该类可以对运行在不同线程中的多个任务进行排队,并使用Message和Runn ...

  5. Java_异常_03_ java.lang.NoClassDefFoundError: org/apache/commons/pool/KeyedObjectPoolFactory

    异常信息: java.lang.NoClassDefFoundError: org/apache/commons/pool/KeyedObjectPoolFactory 原因: 我用的是commons ...

  6. Caused by: java.lang.NoClassDefFoundError: org/apache/commons/pool/BasePoolableObjectFactory

    摘要:异常信息解决过程记录 一:异常信息: Caused by: java.lang.NoClassDefFoundError: org/apache/commons/pool/BasePoolabl ...

  7. Android中使用Thread线程出现的问题

    很多初入Android或Java开发的新手对Thread.Looper.Handler和Message仍然比较迷惑,衍生的有HandlerThread.java.util.concurrent.Tas ...

  8. android java thread_Android中断并重启一个Thread线程的简单方法

    这里简单的总结下(大概思路,没调试,可能会有错!): MyThread.java pulbic class MyThread implemets Thread{ @overide public voi ...

  9. [Android]Thread线程入门3--多线程

    经过 [Android]Thread线程入门1 和[Android]Thread线程入门2 的学习,我们对线程有了简单的了解.在实际应用中,一般都会用到多线程.很少像前面的例子这么简单.那么如何实现多 ...

最新文章

  1. 【Opencv】直方图函数 calchist()
  2. 机器学习多目标分类模型解法
  3. VTK:PolyData之ColorCells
  4. jenkins api_接触Jenkins(Hudson)API,第2部分
  5. unexpected AST node
  6. WebServicenbsp;创建nbsp;nbsp;发布nbsp;调用整个流…
  7. linux将mysql中得配置为可读写_MySQL注入 利用系统读、写文件
  8. 通过python连接mysql模拟成绩查询系统
  9. MariaDB 10之并行复制--延迟测试结果
  10. redis练习-模拟手机验证码的发送
  11. 【UML】概念、关联、画画(一)
  12. 亚马逊、速卖通、temu、国际站卖家如何做自养号测评?干货分享
  13. 三星S5P6818移植工程
  14. html和flash播放器区别,flash播放器和一般播放器有什么区别
  15. 深度学习之美(张玉宏)——第三章 机器学习三重门
  16. 江兴华老师在武汉讲座
  17. 【SSL1607】没有上司的晚会【树形DP】
  18. 图片服务器FastDFS的安装及使用
  19. (二)回顾硅谷:硅谷历史
  20. nodejs项目云端部署初试

热门文章

  1. 经常被遗忘的 iOS 源码仓库 - Code4App
  2. win2012虚拟服务器,Win server2012如何创建虚拟机存储虚拟磁盘
  3. 深度学习网络模型——Vision Transformer详解 VIT详解
  4. 输入年份和月份,打印出这个月有多少天
  5. 快来看看阿里巴巴的常用面试题
  6. 手机通信专有名词中英文对照O-Z
  7. OpenCL的基本介绍
  8. 2014秋C++ 狗逮耗子之“开口说英语”
  9. java radiobutton获取信息_java-如何获取从ButtonGroup中选择的哪个JRadioButton
  10. 电子协会 C语言 1级 35 、银行利息