当 Java 中的线程死亡时,ThreadPoolExecutor 会发生什么

我创建了一个线程,该线程又创建一个ThreadPoolExecutor并向它提交一些长时间运行的任务。在某些时候,原始线程由于未处理的异常/错误而死亡。执行程序应该发生什么(它是那个死线程的本地,没有对其的外部引用)?它应该被GCed还是不被GCed?

编辑:这个问题从一开始就被错误地表述了,但我会留下它,因为 Gray 提供了一些关于 TPE 如何工作的详细信息。


宝慕林4294392
浏览 338回答 2
2回答

翻过高山走不出你

executor 会发生什么(它是死线程的本地,没有外部引用)?它应该被GCed还是不被GCed?答案比“是的,如果没有引用它”更复杂。这取决于在 中运行的线程ThreadPoolExecutor是否仍在运行。这又取决于创建的 TPE 类型以及提交给它的“长时间运行的任务”是否已完成。例如,如果任务尚未完成,则线程仍将运行。即使它们已经完成,如果您的 TPE 具有未设置的核心线程,allowCoreThreadTimeOut(true)那么线程也不会停止。JVM 从不垃圾收集正在运行的线程,因为它们被认为是GC“根”:...根据定义,正在运行的线程对 GC 免疫。GC 通过扫描“根”开始其工作,这些“根”被认为始终可以访问;根包括全局变量(Java-talk 中的“静态字段”)和所有正在运行的线程的堆栈......所以下一个问题是线程是否有引用回,ThreadPoolExecutor我相信他们有。内部Worker类是Runnable存储在其中并由它thread.target执行的,Thread因此它不能被GC'd。 Worker并非static如此,它隐含了对外部ThreadPoolExecutor实例的引用。该run()方法实际上是调用ThreadPoolExecutor.runWorker()引用由ThreadPoolExecutor. 因此,正在运行的线程保留对WorkerTPE 和 TPE 的引用,因此垃圾收集器无法收集 TPE。例如,这是一个引用 TPE 的正在运行的池线程的典型堆栈帧:java.lang.Thread.sleep(Native Method)com.j256.GcTester$1.run(GcTesteri.java:15)java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)java.util.concurrent.FutureTask.run(FutureTask.java:266)>> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)>> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)java.lang.Thread.run(Thread.java:748)但是,如果线程池任务已全部完成并且它有 0 个核心线程或核心线程已超时,那么将没有Worker与ThreadPoolExecutor. 然后 TPE将被垃圾收集,因为除了 GC 足够智能可以检测到的循环引用之外,没有对它的引用。这是一个演示它的小示例测试程序。finalize()如果有 1 个核心线程,那么即使工作线程在注意到/tmp/x文件存在后退出,TPE 也永远不会关闭(通过)。即使主线程没有引用它也是如此。但是,如果有 0 个核心线程,那么在线程超时后(这里是在完成最后一个任务后 1 秒后)TPE 将被收集。public class GcTester {&nbsp; &nbsp; public static void main(String[] args) {&nbsp; &nbsp; &nbsp; &nbsp; int numCore = 1; // set to 0 to have it GC'd once /tmp/x file exists&nbsp; &nbsp; &nbsp; &nbsp; ExecutorService pool =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new ThreadPoolExecutor(numCore, Integer.MAX_VALUE,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; protected void terminated() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(this + " terminated");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; pool.submit(new Runnable() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void run() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while (true) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Thread.sleep(100); // need to handle exception here&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (new File("/tmp/x").exists()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("thread exiting");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; pool = null; // allows it to be gc-able&nbsp; &nbsp; &nbsp; &nbsp; while (true) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Thread.sleep(1000);&nbsp; // need to handle exception here&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.gc();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// pull the big GC handle&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}

守着一只汪

线程就是所谓的GC 根。这意味着无法收集正在运行(或未启动)的线程。这也意味着无法收集从这些线程引用的对象,这就是为什么您可以执行类似的操作new Thread(new MyRunnable()).start(),或者在没有任何引用的情况下运行线程池。如果线程是一个守护进程,如果所有其他非守护线程都停止了,它可以自动停止。您可以拥有带有守护线程的线程池,但最好的办法是确保正确清理事物(即异常不应终止本应最终停止并清理线程池的线程)。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java