目录

  • 问题说明
  • 代码验证:
  • 创建全局的线程池。

线程池是一个很好的使用线程的方式, 但是如果使用不当还是会引起问题的。

问题说明

当一个对象变成垃圾后会被垃圾对象回收, 其内部的成员变量自然也会被回收, 但是如果成员变量是线程池, 那么这个就会出现问题。

  1. 线程池使用后内部还是会维护核心线程存活,而存活的线程不是垃圾对象,反而线程可以作为GC Roots
  2. 如果确实需要局部使用线程变量,请在使用完后一定要调用shutdown方法。 调用了这个方法后,线程池的中的线程不会再维护存活了,可以回收了。 shutdown方法建议在finally模块中执行
  3. 线程池内部 allowCoreThreadTimeOut变量,注意是ThreadPoolExecutor的变量, 不是ExecutorService中定义了。 这个变量默认值是false, 如果设置成true后线程池在keepAlive时间后不会再维护核心线程了。

代码验证:

下面我们使用代码了验证下这个问题:

public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "  启动。。。。");
        });
        executorService.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "  启动。。。。");
        });

        executorService = null;

        System.gc();

        System.out.println("垃圾回收后.........................................");

        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
        allStackTraces.forEach((k, v) -> {
            String name = k.getName();
            System.out.println("存活线程:" + k.getName());
        });
        System.out.println("程序运行结束后.........................................");
    }

运行后的输出如下:

java如何保证循环里创建的线程的参数有效性_java

为何会是这个情况, 我根据自己的理解逐个说明下:

  • 为何垃圾回收语句最先打印?
    因为线程的创建是一个耗资源的过程, 线程池中的两个线程需要经历创建栈空间,竞争CPU资源,竞争锁资源。 当这些准备工作都完成后才会开始打印日志。 但是此时主线程已经是运行中的状态, 它更容易获取到cpu和锁, 所以垃圾回收的语句先打印出来。

我们改下代码再次运行下:

public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "  启动。。。。");
        });
        executorService.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "  启动。。。。");
        });

        executorService = null;
        //增加暂停3秒的语句
        Thread.sleep(3000);
        System.gc();

        System.out.println("垃圾回收后.........................................");

        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
        allStackTraces.forEach((k, v) -> {
            String name = k.getName();
            System.out.println("存活线程:" + k.getName());
        });
        System.out.println("程序运行结束后.........................................");
    }

运行后的结果如下:

java如何保证循环里创建的线程的参数有效性_System_02

可以看到日志打印的顺序正常了。 但是还是有其他的问题。

  • 垃圾回收后线程池的线程尽然还是存活的?
    垃圾回收对于线程是无效的, 线程池中的线程继续存活者。
  • 主线程结束后, 整个程序还不会结束?
    这说明线程池中的线程不是守护线程, 不会随着主线程的结束而结束的。

创建全局的线程池。

因为线程池不能被简单回收,所以干脆把线程池作为静态变量, 全局享用, 每次我们使用线程池的时候, 看下全局中是否已经有了, 如果已经有了就不要在创建新的。

可以把线程池统一放到一个单例类中管理, 统一管理线程池的创建和销毁, 防止在代码中忘记关闭线程池。

给大家分享个我经常使用的全局线程池类:

import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public final class ThreadPoolUtil {

    private void ThreadPoolUtil() {

    }

    static volatile int theadCount = 0;

    /**
     * 通用的线程池。
     */
    public static ThreadPoolExecutor commonPool = new ThreadPoolExecutor(1,
            500, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5000)
            , r -> new Thread(r, "common thread " + theadCount++)
    );

    /**
     * 定时任务线程池, 注意定时任务要设置合理的定时时间, 要根据任务的耗时来合理设置。 建议定时任务还是使用spring的定时任务功能
     */
    public static ScheduledThreadPoolExecutor schedulePool = new ScheduledThreadPoolExecutor(1,
            r -> new Thread(r, "schedulePool thread " + theadCount++)
    );

    /**
     * 短时间批量任务的执行
     *
     * @param poolSize
     * @param runnables
     */
    public static void createPool(int poolSize, List<Runnable> runnables) {
        if (poolSize < 1) {
            throw new RuntimeException("请设置合理的线程池数量");
        }
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(poolSize,
                poolSize, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(runnables.size())
                , r -> new Thread(r, "create new Pool thread " + theadCount++));

        runnables.forEach(runnable -> {
            threadPoolExecutor.execute(runnable);
        });
        //这里只是通知线程池不在接收新任务了,并且所有任务结束后,线程池要关闭
        threadPoolExecutor.shutdown();
    }

}

当然你们可以根据自己的情况灵活调整线程池的参数。