【视频笔记】巧用线程工厂回答:函数中创建的线程池会被GC回收吗?
在函数中创建的线程池会被gc回收吗?
创建一个线程池,给线程池执行一个任务,然后在main函数中调用了这个函数。
在execute函数结束的时候,没有任何人去引用这个executor,那么这个线程池会被回收吗?
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class GcTest { public static void main(String[] args) { execute(); } private static void execute() { ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024)); executor.execute(() -> { System.out.println("run some task"); }); } }
如果判断一个对象是否被回收呢?我们可以通过弱引用来判断
创建一个弱引用,然后这个对象里指向了这只猫。jvm中当且仅当只有这个弱引用指向这只猫的时候,这只猫就会被回收。
public static void main(String[] args) { Cat cat = new Cat(); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<Cat> catWeakReference = new WeakReference<>(cat); System.gc(); if(catWeakReference.get() == null) { System.out.println("cat已经被回收了"); } }
控制台什么都没有看见,说明它没有被回收。
为什么没有被回收,因为方法里有cat,指向了new的这个Cat。
在gc()回收之前,断掉cat引用,赋值为null,再次运行,打印了“cat已经被回收了”,说明已经被回收了。
public static void main(String[] args) { Cat cat = new Cat(); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<Cat> catWeakReference = new WeakReference<>(cat); cat = null; System.gc(); if(catWeakReference.get() == null) { System.out.println("cat已经被回收了"); } }
也就是说,如果当前没有任何的引用指向我们被弱引用包装的对象,那么我们就可以判断,一个对象是否可以被回收。
把上述代码中的cat对象,换成前面的线程池,发现没有打印已回收的字样。而且程序没有停止。手动停止掉
public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024)); executor.execute(() -> { System.out.println("run some task"); }); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<ThreadPoolExecutor> catWeakReference = new WeakReference<>(executor); executor = null; System.gc(); if(catWeakReference.get() == null) { System.out.println("executor已经被回收了"); } }
把执行任务那句代码注释掉,再运行代码,打印了已回收字样。
说明线程池在执行任务之前和前面那只cat是一样的,那执行任务过程中到底发生了什么呢?
为什么在执行了execute之后,线程池就不回收了。
分析这个问题,需要两个前置知识:
1、在java运行程序中,什么对象会被回收?答案只有一个,gc root不可达的对象会被回收。
2、大家心里有一个线程池的基本流程
线程池execute方法会在背后创建一个核心线程,然后执行一个runnable函数,这个runnable函数并不是我们传进来的那个runnable(即lambda表达式() -> {System.out.println("run some task")), 而是一个while循环, 这个while循环会不断的从这个阻塞队列里(new ArrayBlockingQueue<>(1024))拿到任务然后去执行,这也是线程池能复用线程的关键所在。
线程池创建时,一个7个参数,这里写了5个,还有一个拒绝策略、一个线程工厂。
在这里我们巧用一下线程工厂,来分析一下写的这段代码。
我们在执行execute的时候,我们会创建一个核心线程,这个核心线程就是由我们的线程工厂来创建的。
在整个逻辑里,只需要创建一个线程,给它命个名就可以。
public class GcTest { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024), new MyThreadFactory()); executor.execute(() -> { System.out.println("run some task, thread name:" + Thread.currentThread().getName()); }); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<ThreadPoolExecutor> catWeakReference = new WeakReference<>(executor); executor = null; System.gc(); if (catWeakReference.get() == null) { System.out.println("executor已经被回收了"); } } static class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { // 给线程命名 return new Thread(r, "my-thread-factory-thread"); } } }
现在这个逻辑和之前一样,区别是这个线程池中的线程是我们创建的,而不是jdk的线程池帮我们创建的。
因为现在我们可以干扰我们创建线程的生命周期,创建线程和线程真正执行任务,中间是有一个过程的,线程的状态是从创建,到真正的执行。
改造一下代码,把我们包装的runnable对象传进thread里去,而不是把那个死循环放到我们的线程里
static class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Runnable newRunnable = () -> { System.out.println("任务真正执行了"); r.run(); }; // 给线程命名 Thread thread = new Thread(newRunnable, "my-thread-factory-thread"); System.out.println("my-thread-factory-thread线程创建了"); return thread; } }
因为线程本身是一个GC root,但是线程是由线程工厂创建的,线程工厂只是线程池里的一个属性,它并不影响线程池被回收,
所以我们有理由相信,我们的线程和线程池之间的关系,就是通过Runnable去维系的(public Thread newThread(Runnable r)),
那我们把这个r.run();注释掉,线程池仍然是可能被回收的,打印结果印证了这点
import java.lang.ref.WeakReference; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class GcTest { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024), new MyThreadFactory()); executor.execute(() -> { System.out.println("run some task, thread name:" + Thread.currentThread().getName()); }); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<ThreadPoolExecutor> catWeakReference = new WeakReference<>(executor); executor = null; System.gc(); if (catWeakReference.get() == null) { System.out.println("executor已经被回收了"); } } static class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Runnable newRunnable = () -> { System.out.println("任务真正执行了"); // r.run(); System.out.println("这个线程已经结束了,等待回收"); }; // 给线程命名 Thread thread = new Thread(newRunnable, "my-thread-factory-thread"); System.out.println("my-thread-factory-thread线程创建了"); return thread; } } }

这个Runnable参数就是一个死循环,不断的从阻塞队列里去拿任务,这个runnable里去和线程池做了一个关联,所以这里我们打个断点,看下传进来的runnable到底是什么,它是怎么样和我们的线程池产生关联的。
这个runnable是一个线程池的内部类ThreadPoolExecutor$Worker,


这个Worker是一个线程池的内部类,我们知道,非静态的内部类是会持有主类的对象
也就是我们的线程,它里面有一个runnable,这个runnable是一个内部类,于是这个runnable引用了当前这对象(线程池)
画一下这个引用链
GC root是我们的thread, Thread -> worker -> ThreadPoolExecutor
所以在这两个位置,断掉任何一个链,我们就可以让我们的线程池被回收。
第一段链,可以通过类似把r.run();注释掉的方式把它断掉;
第二段链,jdk的线程池,它的依赖非常多,所以我们很难把它的worker改掉( 要改只能使用自定义的线程池)
把线程池改成自定义的线程池
public class GcTest { public static void main(String[] args) { MyThreadPool myThreadPool = new MyThreadPool(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024), new DiscardRejectHandle()); myThreadPool.execute(() -> { System.out.println("run some task, thread name:" + Thread.currentThread().getName()); }); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<MyThreadPool> weakReference = new WeakReference<>(myThreadPool); myThreadPool = null; System.gc(); if (weakReference.get() == null) { System.out.println("executor已经被回收了"); } else { System.out.println("executor沒被回收"); } } }
打印:executor沒被回收
因为我们的线程里面也有一个我们的内部类,只要把我们的内部类变成静态的内部类,我们就把这部分(worker -> ThreadPoolExecutor)的引用切断了
import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; public class MyThreadPool { private final int coolPoolSize; private final int maxPoolSize; private final int timeout; private final TimeUnit timeUnit; private final BlockingQueue<Runnable> tasks; private final RejectHandle rejectHandle; public MyThreadPool(int coolPoolSize, int maxPoolSize, int timeout, TimeUnit timeUnit, BlockingQueue<Runnable> tasks, RejectHandle rejectHandle) { this.coolPoolSize = coolPoolSize; this.maxPoolSize = maxPoolSize; this.timeout = timeout; this.timeUnit = timeUnit; this.tasks = tasks; this.rejectHandle = rejectHandle; } public BlockingQueue<Runnable> getTasks() { return tasks; } List<Thread> coreList = new ArrayList<>(); // maxPoolSize - coolPoolSize List<Thread> supportList = new ArrayList<>(); public void execute(Runnable runnable) { if (coreList.size() < coolPoolSize) { Thread thread = new CoreThread(runnable, tasks); coreList.add(thread); thread.start(); return; } // 如果放入阻塞队列返回false,说明我们的队列已经满了 // 当阻塞队列满了的时候,我们需要用一些辅助线程,来帮我们的核心线程处理这些任务 if (tasks.offer(runnable)) { return; } if (coreList.size() + supportList.size() < maxPoolSize) { Thread thread = new SupportThread(runnable); supportList.add(thread); thread.start(); return; } if (!tasks.offer(runnable)) { rejectHandle.reject(runnable, this); } } static class CoreThread extends Thread { private final Runnable firstTask; private final BlockingQueue<Runnable> tasks; public CoreThread(Runnable firstTask, BlockingQueue<Runnable> tasks) { this.firstTask = firstTask; this.tasks = tasks; } public void run() { firstTask.run(); while (true) { Runnable command = null; try { command = tasks.take(); command.run(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } class SupportThread extends Thread { private final Runnable firstTask; public SupportThread(Runnable firstTask) { this.firstTask = firstTask; } public void run() { firstTask.run(); while (true) { Runnable command = null; try { command = tasks.poll(timeout, timeUnit); if (command == null) { break; } command.run(); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + "线程结束了"); } } }
变成静态之后,控制台打印:executor已经被回收了。
总结:
在函数中创建的线程池会被回收吗?
如果这个线程池创建过线程,那么就不会回收。因为线程池的生命周期和线程是一样的;如果没创建过线程,那就会回收。
原因是线程池创建的线程里面的runnable是一个worker,这个worker是一个内部类,这个内部类里面有线程池的引用。
为了证实这点,分别尝试切断这2个引用(Thread -> worker -> ThreadPoolExecutor),都成功了。
线程工厂,不光是给线程命名,可以通过它对线程任务进行封装,甚至可以达到对线程池的线程的生命周期达到监控的效果。
思考题:
函数内的线程池创建线程之后,shutdown了,会回收吗,为什么?
public static void main(String[] args) { ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024)); myThreadPool.execute(() -> { System.out.println("run some task, thread name:" + Thread.currentThread().getName()); }); // 创建一个弱引用,然后这个对象里指向了这只猫 WeakReference<ThreadPoolExecutor> weakReference = new WeakReference<>(myThreadPool); // myThreadPool = null; myThreadPool.shutdown(); System.gc(); if (weakReference.get() == null) { System.out.println("executor已经被回收了"); } else { System.out.println("executor沒被回收"); } }
打印: executor沒被回收
再加上myThreadPool = null; 也仍打印没被回收。//TODO
超级无敌大鹏精
老粉
这一期的内容让我想起了前不久遇到的一个问题,我们公司又一个代码扫描工具,项目中有一个类,它有一个非静态的内部内类,这个内部类实现了序列化接口。扫描的时候就扫出漏洞了,说非静态的内部类会持有外部类的引用,序列化和反序列化过程可能会泄露外部类的信息或抛出运行时异常,改成静态的就可以了
2025-04-01 22:00
回复
学java的生生
还有一个就是lambda也会持有宿主,曾经踩过一个初始化的坑.. 放进了lambda里面类还没初始化完成,直接死锁了..
查出问题之后我就这个表情[辣眼睛]
凉宫物語
讲得真好,课后小问题的话线程池Shutdown会中断所有空闲线程,那worker应该会断开引用,如果队列里没有任务在执行,感觉是可以回收
2025-03-21 21:33
回复
学java的生生
没错!
浙公网安备 33010602011771号