前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊java 线程池回收

聊聊java 线程池回收

作者头像
山行AI
发布2019-06-28 12:04:42
3.1K1
发布2019-06-28 12:04:42
举报
文章被收录于专栏:山行AI

先来看下面这个实例:

代码语言:javascript
复制
public class SimpleTask {
    private final int mIndex;
    private Executor mExecutors = Executors.newSingleThreadExecutor();

    public SimpleTask(int index) {
        mIndex = index;
    }

    public void runTask(int y) {
        mExecutors.execute(() -> {
        	System.out.println(mIndex);
        	//System.out.println(y);
            System.out.println(Thread.currentThread().getName());
        });

    }

    public Executor getmExecutors() {
        return mExecutors;
    }
}

public class ThreadPoolTest {

	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new SimpleTask(i).runTask(i);
		}
		System.out.println(Thread.currentThread().getName());
		System.gc();
		//		try {
        //			TimeUnit.SECONDS.sleep(10);
        //		} catch (InterruptedException e) {
        //			e.printStackTrace();
        //		}
	}

}

执行结果为:

结果中可以看出,main执行结束了,但是整个程序处于挂起状态。 通过jmap -histo 可以看到:

  • 在程序中共创建了10个SimpleTask对象,每个对象有一个ThreadPoolExecutor实例,可以看到SimpleTask对象还有两个未被回收(因为SimpleTask的成员变量mIndex被com.rt.platform.infosys.market.SimpleTask$$Lambda$1/792791759的两个实例使用),ThreadPoolExecutor 对象实例还有10个等待回收。
  • 由于有两个SimpleTask实例存在(这两个实例都是在主程序中new的),主程序不会关闭,里面的线程池都不会被回收,会继续执行。
  • 虽然还有两个SimpleTask实例,但是线程池实例数量仍然是10个。

执行下面代码,将SimpleTask的runTask(int y)改成下面这样:

代码语言:javascript
复制
 public void runTask(int y) {
        mExecutors.execute(() -> {
        	//System.out.println(mIndex);
        	System.out.println(y);
           System.out.println(Thread.currentThread().getName());
        });

    }

程序直接结束,结果如下:

代码语言:javascript
复制
0
2
pool-3-thread-1
3
pool-4-thread-1
1
pool-2-thread-1
5
pool-6-thread-1
main
4
pool-5-thread-1
pool-1-thread-1
9
pool-10-thread-1
7
pool-8-thread-1
8
pool-9-thread-1
6
pool-7-thread-1

在main方法中加上sleep逻辑,让主程序挂起一段时间,然后执行jmap -histo : main方法代码如下:

代码语言:javascript
复制
public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new SimpleTask(i).runTask(i);
		}
		System.out.println(Thread.currentThread().getName());
		System.gc();
		try {
			TimeUnit.SECONDS.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

然后执行jmap -histo :

代码语言:javascript
复制
 226:             4             64  com.rt.platform.infosys.market.SimpleTask$$Lambda$1/792791759
  403:             1             16  java.util.concurrent.ThreadPoolExecutor$AbortPolicy
   36:            18           6768  java.lang.Thread
   84:            10            720  java.util.concurrent.ThreadPoolExecutor
   98:            10            480  java.util.concurrent.LinkedBlockingQueue
   99:            10            480  java.util.concurrent.ThreadPoolExecutor$Worker
  100:            30            480  java.util.concurrent.locks.ReentrantLock
  106:             1            384  com.intellij.rt.execution.application.AppMainV2$1
  • 可以看到,这时候SimpleTask对象已经全部被回收完毕。但是线程池实例仍然存在。
  • 之所以SimpleTask对象都被回收是因为这里SimpleTask里的另一个成员变量mIndex没有在com.rt.platform.infosys.market.SimpleTask$$Lambda$1/792791759实例内使用,所以SimpleTask在调用System.gc时会都被回收掉。
  • com.rt.platform.infosys.market.SimpleTask$$Lambda$1/792791759的4个实例是局部变量,方法执行结束后会被回收,程序结束后,线程池会被关闭。
  • 虽然没有SimpleTask实例存在,但是仍然有10个ThreadPoolExecutor存在。

2. 线程池作为局部变量

代码改成这样:

代码语言:javascript
复制
public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			ExecutorService executorService = Executors.newSingleThreadExecutor();
			int finalI = i;
			executorService.execute(() -> {
				SimpleTask simpleTask = new SimpleTask(finalI);
				System.out.println(simpleTask.getmIndex());
			});
		}
		try {
		//给System.gc()提供一定的时间
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName());
		System.gc();
		try {
			TimeUnit.SECONDS.sleep(30);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

结果为:

代码语言:javascript
复制
9
1
5
2
3
8
0
4
7
6
main

执行jmap -histo 结果为:

代码语言:javascript
复制
 84:            10            720  java.util.concurrent.ThreadPoolExecutor
 98:            10            480  java.util.concurrent.LinkedBlockingQueue
 99:            10            480  java.util.concurrent.ThreadPoolExecutor$Worker
100:            30            480  java.util.concurrent.locks.ReentrantLock
  • SimpleTask实例已经被回收。
  • ThreadPoolExecutor也有10个实例,未被回收。

3.为什么实例回收了,但是线程池还是没有关闭呢?线程池作为局部变量被使用时,为什么也没有被回收呢?

线程池无法被回收,是因为线程池的引用被它的内部类 Worker 持有了。而 Worker 和线程一一对应,是对 Thread 的增强,所以本质上就是因为线程没有被释放。 看java.util.concurrent.ThreadPoolExecutor.runWorker代码:

代码语言:javascript
复制
 /**
     * Main worker run loop.  Repeatedly gets tasks from queue and
     * executes them, while coping with a number of issues:
     *
     * 1. We may start out with an initial task, in which case we
     * don't need to get the first one. Otherwise, as long as pool is
     * running, we get tasks from getTask. If it returns null then the
     * worker exits due to changed pool state or configuration
     * parameters.  Other exits result from exception throws in
     * external code, in which case completedAbruptly holds, which
     * usually leads processWorkerExit to replace this thread.
     *
     * 2. Before running any task, the lock is acquired to prevent
     * other pool interrupts while the task is executing, and then we
     * ensure that unless pool is stopping, this thread does not have
     * its interrupt set.
     *
     * 3. Each task run is preceded by a call to beforeExecute, which
     * might throw an exception, in which case we cause thread to die
     * (breaking loop with completedAbruptly true) without processing
     * the task.
     *
     * 4. Assuming beforeExecute completes normally, we run the task,
     * gathering any of its thrown exceptions to send to afterExecute.
     * We separately handle RuntimeException, Error (both of which the
     * specs guarantee that we trap) and arbitrary Throwables.
     * Because we cannot rethrow Throwables within Runnable.run, we
     * wrap them within Errors on the way out (to the thread's
     * UncaughtExceptionHandler).  Any thrown exception also
     * conservatively causes thread to die.
     *
     * 5. After task.run completes, we call afterExecute, which may
     * also throw an exception, which will also cause thread to
     * die. According to JLS Sec 14.20, this exception is the one that
     * will be in effect even if task.run throws.
     *
     * The net effect of the exception mechanics is that afterExecute
     * and the thread's UncaughtExceptionHandler have as accurate
     * information as we can provide about any problems encountered by
     * user code.
     *
     * @param w the worker
     */
    final void runWorker(Worker w) {
   	 Thread wt = Thread.currentThread();
   	 Runnable task = w.firstTask;
   	 w.firstTask = null;
   	 w.unlock(); // allow interrupts
   	 boolean completedAbruptly = true;
   	 try {
   		 while (task != null || (task = getTask()) != null) {
   			 w.lock();
   			 // If pool is stopping, ensure thread is interrupted;
   			 // if not, ensure thread is not interrupted.  This
   			 // requires a recheck in second case to deal with
   			 // shutdownNow race while clearing interrupt
   			 if ((runStateAtLeast(ctl.get(), STOP) ||
   				  (Thread.interrupted() &&
   				   runStateAtLeast(ctl.get(), STOP))) &&
   				 !wt.isInterrupted())
   				 wt.interrupt();
   			 try {
   				 beforeExecute(wt, task);
   				 Throwable thrown = null;
   				 try {
   					 task.run();
   				 } catch (RuntimeException x) {
   					 thrown = x; throw x;
   				 } catch (Error x) {
   					 thrown = x; throw x;
   				 } catch (Throwable x) {
   					 thrown = x; throw new Error(x);
   				 } finally {
   					 afterExecute(task, thrown);
   				 }
   			 } finally {
   				 task = null;
   				 w.completedTasks++;
   				 w.unlock();
   			 }
   		 }
   		 completedAbruptly = false;
   	 } finally {
   		 processWorkerExit(w, completedAbruptly);
   	 }
    }

如果想要执行processWorkerExit方法需要满足两个条件:

  1. getTask获取到的为null,getTask代码如下:
代码语言:javascript
复制
 private Runnable getTask() {
   	 boolean timedOut = false; // Did the last poll() time out?

   	 for (;;) {
   		 int c = ctl.get();
   		 int rs = runStateOf(c);

   		 // Check if queue empty only if necessary.
   		 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
   			 decrementWorkerCount();
   			 return null;
   		 }

   		 int wc = workerCountOf(c);

   		 // Are workers subject to culling?
   		 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

   		 if ((wc > maximumPoolSize || (timed && timedOut))
   			 && (wc > 1 || workQueue.isEmpty())) {
   			 if (compareAndDecrementWorkerCount(c))
   				 return null;
   			 continue;
   		 }

   		 try {
   			 Runnable r = timed ?
   				 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
   				 workQueue.take();
   			 if (r != null)
   				 return r;
   			 timedOut = true;
   		 } catch (InterruptedException retry) {
   			 timedOut = false;
   		 }
   	 }
    }

有两种情况getTask会为null:

  • rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()):调用 shutdown 或者 shutdownNow 方法将线程状态置为SHUTDOWN或者STOP;
  • (wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty()) :当前线程数大于核心线程或者超时两个中的一个成立并且当前核心线程大于1和工作队列为空两者一个成立的时候。
  1. 我们没有调用shutdown,第一个条件不满足。
  2. 我们没有对核心线程设置超时,而且是单个线程,所以wc > maximumPoolSize || (timed && timedOut)不满足

4. 总结

  • 线程池使用时一般使用全局单例形式,以免浪费资源;
  • 全局线程池在程序结束时会被回收,也可以使用spring这类框架提供的线程池,它提供了线程池回收的机制;
  • 如果需要局部使用线程池,应该设置核心线程池的超时时间或者手动shutdown。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 线程池作为局部变量
  • 3.为什么实例回收了,但是线程池还是没有关闭呢?线程池作为局部变量被使用时,为什么也没有被回收呢?
  • 4. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档