前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jedis:commons-pool-evictor-thread线程不能自动关闭?

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

作者头像
10km
发布2019-05-25 20:33:56
2.6K0
发布2019-05-25 20:33:56
举报
文章被收录于专栏:10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433436

jedis为了管理网络连接使用了apache commons-pool用于连接资源管理,我使用jedis版本是2.8.2,依赖的commons-pool 版本是2.4.2

之前我的项目中调用jedis时,都是添加ShutdownHook在程序结束时自动关闭JedisPool.就是类似下面的代码:

代码语言:javascript
复制
	static{
		// 程序退出时自动销毁连接池对象
		Runtime.getRuntime().addShutdownHook(new Thread(){
			@Override
			public 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类的初始化

代码语言:javascript
复制
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线程的代码

代码语言:javascript
复制
    /**
     * {@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}
         */
        @Override
        public 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线程的代码是这样的:

代码语言:javascript
复制
    /**
     * Thread factory that creates a thread, with the context classloader from this class.
     */
    private static class EvictorThreadFactory implements ThreadFactory {

        @Override
        public 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>() {
                @Override
                public 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类的代码片段

代码语言:javascript
复制
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() {
  }
  @Override
  public 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.javaclose()方法实现

代码语言:javascript
复制
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()}.
     */
    @Override
    public void close() {
        if (isClosed()) {
            return;
        }

        synchronized (closeLock) {
            if (isClosed()) {
                return;
            }

            // 资源池关闭之前先停止Evictor线程
            startEvictor(-1L);

            closed = true;
            // This clear removes any idle objects
            clear();

            jmxUnregister();

            // Release any threads that were waiting for an object
            idleObjects.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线程的代码片段:

代码语言:javascript
复制
    /**
     * Thread factory that creates a daemon thread, with the context class loader from this class.
     */
    private static class EvictorThreadFactory implements ThreadFactory {

        @Override
        public 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.2019
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                @Override
                public Void run() {
                    thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());
                    return null;
                }
            });

            return thread;
        }
    }

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

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • commons-pool-evictor-thread线程
  • 为什么commons-pool-evictor-thread线程的类型居然不一样?
  • 解决方案
    • 使用commons-pool 2.4.2版本
      • 显式执行JedisPool#close()方法
      • 后记
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档