首先看一个简单的例子:代码可能会抛出空指针异常,但这个异常就会被吞掉。

Java线程池的这几个大坑,你踩过几个?_System

要优雅解决问题,可以为线程池设置一个全局的异常处理器,使用自定义线程工厂来设置!

public class CustomThreadFactory implements ThreadFactory {
    private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = defaultFactory.newThread(r);
        
        // 设置全局异常处理器
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.err.println("Thread " + t.getName() + " threw exception: " + e.getMessage());
        });
        return thread;
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2, new CustomThreadFactory());
        // 提交任务到线程池
        executorService.execute(() -> {
            throw new NullPointerException("Test Exception");
        });
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
拒绝策略设置错误导致接口超时!

如果没有正确设置拒绝策略,可能会导致接口超时或服务中断。

public class RejectionPolicyExample {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                1, // 核心线程数
                1, // 最大线程数
                0L, // 空闲线程存活时间
                TimeUnit.MILLISECONDS, // 存活时间单位
                new LinkedBlockingQueue<>(1), // 任务队列
                Executors.defaultThreadFactory(), // 线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 默认拒绝策略
        );
        
        // 提交三个任务到线程池,第三个任务将被拒绝
        for (int i = 0; i < 3; i++) {
            executorService.execute(() -> {
                try {
                    Thread.sleep(1000); // 模拟任务执行
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(Thread.currentThread().getName() + " executed task");
            });
        }
        
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

合适的拒绝策略如CallerRunsPolicy可以使任务在调用者线程中执行,从而避免任务丢失。

Java线程池的这几个大坑,你踩过几个?_System_02

public class CallerRunsPolicyExample {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                1, // 核心线程数
                1, // 最大线程数
                0L, // 空闲线程存活时间
                TimeUnit.MILLISECONDS, // 存活时间单位
                new LinkedBlockingQueue<>(1), // 任务队列
                Executors.defaultThreadFactory(), // 线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy() // 调用者运行策略
        );
        
        // 提交三个任务到线程池,第三个任务将在调用者线程中执行
        for (int i = 0; i < 3; i++) {
            executorService.execute(() -> {
                try {
                    Thread.sleep(1000); // 模拟任务执行
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(Thread.currentThread().getName() + " executed task");
            });
        }
        
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
重复创建线程池导致内存溢出

重复创建线程池会导致系统资源浪费,甚至引发内存溢出。

public class DuplicateThreadPoolExample {
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            
            // 提交任务到线程池
            executorService.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " executed task");
            });
            
            // 关闭线程池
            executorService.shutdown();
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

那当然我们整个应用中只使用一个线程池实例。

public class SingletonThreadPoolExample {
    // 单例线程池
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static ExecutorService getExecutorService() {
        return executorService;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            // 使用单例线程池提交任务
            getExecutorService().execute(() -> {
                System.out.println(Thread.currentThread().getName() + " executed task");
            });
        }
        
        // 关闭线程池
        getExecutorService().shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
共用线程池执行不同任务

在同一个线程池中执行不同性质的任务,可能会导致任务相互影响,进而降低系统效率。例如,CPU密集型IO密集型任务混用同一线程池,效率会大打折扣。

public class MixedTasksExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // 提交CPU密集型任务
        executorService.execute(() -> {
            for (int i = 0; i < 1000000; i++) {} // 模拟CPU密集型任务
            System.out.println(Thread.currentThread().getName() + " executed CPU-intensive task");
        });
        // 提交IO密集型任务
        executorService.execute(() -> {
            try {
                Thread.sleep(2000); // 模拟IO密集型任务
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() + " executed IO-intensive task");
        });
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

解决方案

为不同任务分配独立的线程池,确保任务执行的高效性。

public class SeparateTasksExample {
    // CPU密集型任务的线程池
    private static final ExecutorService cpuIntensivePool = Executors.newFixedThreadPool(5);
    // IO密集型任务的线程池
    private static final ExecutorService ioIntensivePool = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {
        // 提交CPU密集型任务
        cpuIntensivePool.execute(() -> {
            for (int i = 0; i < 1000000; i++) {} // 模拟CPU密集型任务
            System.out.println(Thread.currentThread().getName() + " executed CPU-intensive task");
        });
        
        // 提交IO密集型任务
        ioIntensivePool.execute(() -> {
            try {
                Thread.sleep(2000); // 模拟IO密集型任务
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() + " executed IO-intensive task");
        });
        
        // 关闭线程池
        cpuIntensivePool.shutdown();
        ioIntensivePool.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
ThreadLocal与线程池的冲突

因为线程池中的线程是复用的,ThreadLocal中的变量可能会被其他任务不小心修改未及时清理

public class ThreadLocalIssueExample {
    private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        
        // 提交任务到线程池
        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                int value = threadLocal.get();
                System.out.println(Thread.currentThread().getName() + " initial value: " + value);
                threadLocal.set(value + 1);
                System.out.println(Thread.currentThread().getName() + " updated value: " + threadLocal.get());
            });
        }
        
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

那当然在任务执行完毕后,及时清理ThreadLocal变量

public class ThreadLocalSolutionExample {
    private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        
        // 提交任务到线程池
        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                try {
                    int value = threadLocal.get();
                    System.out.println(Thread.currentThread().getName() + " initial value: " + value);
                    threadLocal.set(value + 1);
                    System.out.println(Thread.currentThread().getName() + " updated value: " + threadLocal.get());
                } finally {
                    // 任务完成后清理ThreadLocal变量
                    threadLocal.remove();
                }
            });
        }
        
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!