【视频笔记】巧用线程工厂回答:函数中创建的线程池会被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的生生

没错!

 

参考:【不背八股】巧用线程工厂回答:函数中创建的线程池会被GC回收吗?_哔哩哔哩_bilibili

posted @ 2025-06-09 20:55  fanblog  阅读(23)  评论(0)    收藏  举报