Java多线程入门(下):线程池——线程的现代化工厂
Java多线程入门下线程池——线程的现代化工厂想象一下你开了一家快递公司。如果每来一个包裹就招一个快递员等包裹送完就辞退——那公司迟早破产。正确的做法是养一支固定的快递员队伍来活了就分配没活就待命。这就是线程池的核心思想。一、什么是线程池线程池Thread Pool是一种可以复用线程的技术。如果没有线程池每次有任务就new Thread()任务跑完线程就销毁。当请求量很大时不断创建和销毁线程会带来巨大的开销服务器甚至会因为资源耗尽而崩溃。线程池的做法是提前创建好一批线程放在池子里有任务来了就从池子里取一个线程去执行执行完不销毁继续待命。线程池 vs 手动创建线程手动创建线程来一个任务招一个工人做完辞退。成本高、效率低。线程池养一支固定队伍循环利用。成本低、可控性强。线程池的任务队列只能放Runnable和Callable对象。二、创建线程池ThreadPoolExecutor在 Java 中创建线程池最标准的方式是使用ThreadPoolExecutor的构造方法。它一共有 7 个参数乍一看很多但理解了每个参数的含义配置起来并不难。7个参数详解publicThreadPoolExecutor(intcorePoolSize,// 核心线程数intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 临时线程存活时间TimeUnitunit,// 存活时间单位BlockingQueueRunnableworkQueue,// 任务队列ThreadFactorythreadFactory,// 线程工厂RejectedExecutionHandlerhandler// 拒绝策略)参数一corePoolSize核心线程数含义线程池中常驻的正式员工数量。即使它们空闲也不会被回收除非设置了allowCoreThreadTimeOut。常用值CPU 密集型任务大量计算CPU 核数 1IO 密集型任务网络请求、文件读写CPU 核数 * 2如何查看 CPU 核数Runtime.getRuntime().availableProcessors()参数二maximumPoolSize最大线程数含义线程池允许创建的最大线程总数核心线程 临时线程。当任务队列满了且当前线程数小于此值时会创建临时工来应急。常用值通常与corePoolSize相同或 IO 密集型场景下设为 CPU 核数的 2~4 倍。参数三keepAliveTime临时线程存活时间含义非核心线程临时工在空闲状态下能存活的最大时长。超过这个时间没活干就会被销毁以节省资源。常用值60秒。参数四unit时间单位含义配合keepAliveTime使用指定存活时间的计量单位。常用值TimeUnit.SECONDS。参数五workQueue任务队列含义存放等待执行任务的缓冲池。常用值new LinkedBlockingQueue(1000)容量视业务而定。⚠️强烈建议使用有界队列指定容量防止任务无限堆积导致内存溢出OOM。参数六threadFactory线程工厂含义用于创建新线程的工厂。主要作用是给线程起个有意义的名字方便排查问题以及设置守护线程状态。常用值Executors.defaultThreadFactory()或自定义实现。参数七handler拒绝策略含义当线程池和队列都满了时的兜底方案——新任务来了该怎么处理策略说明AbortPolicy丢弃任务并抛出RejectedExecutionException异常。这是默认策略。DiscardPolicy丢弃任务但不抛异常。不推荐因为静默丢任务很难排查。DiscardOldestPolicy丢弃队列里等待最久的那个老任务然后尝试把当前任务加进去。CallerRunsPolicy由提交任务的线程通常是主线程亲自执行这个任务。CallerRunsPolicy 的妙处它不丢任务也不报错只是会让提交者慢下来起到一种简单的限流作用。完整代码演示packageduoxiancheng;importjava.util.concurrent.*;publicclassThreadPoolDemo{publicstaticvoidmain(String[]args){// 创建一个线程池ThreadPoolExecutorpoolnewThreadPoolExecutor(3,// 核心线程数3个正式工5,// 最大线程数最多5个人3个正式工 2个临时工60,// 临时工空闲60秒后被辞退TimeUnit.SECONDS,newLinkedBlockingQueue(100),// 任务队列最多缓冲100个任务Executors.defaultThreadFactory(),// 默认线程工厂newThreadPoolExecutor.CallerRunsPolicy()// 拒绝策略让主线程自己干);// 提交 10 个任务for(inti1;i10;i){inttaskIdi;pool.execute(()-{System.out.println(任务 taskId 正在执行执行线程Thread.currentThread().getName());try{Thread.sleep(1000);// 模拟任务耗时}catch(InterruptedExceptione){e.printStackTrace();}});}// 关闭线程池不再接受新任务等已有任务执行完pool.shutdown();}}线程池的常用方法方法说明execute(Runnable)提交一个 Runnable 任务无返回值。submit(CallableT)提交一个 Callable 任务返回FutureT可以获取执行结果。shutdown()优雅关闭不再接受新任务但会等队列里已有的任务执行完。shutdownNow()强制关闭尝试中断所有正在执行的线程返回队列中未执行的任务列表。// submit() 示例提交 Callable 并获取结果FutureIntegerfuturepool.submit(()-{return123;});Integerresultfuture.get();// 阻塞等待结果System.out.println(计算结果result);线程池的执行流程当有任务提交时线程池按以下规则处理核心线程数未满创建新线程执行任务核心线程已满任务进入任务队列排队队列已满创建临时线程不超过 maximumPoolSize临时线程也满了触发拒绝策略。关键结论只有当核心线程都在忙 “任务队列满了” “当前线程数 maximumPoolSize” 时才会创建临时线程。只有当所有线程都在忙 “任务队列也满了” 时才会拒绝任务。三、Executors 工具类看起来方便实则埋雷Java 还提供了一个工具类Executors可以一行代码创建线程池ExecutorServicepool1Executors.newFixedThreadPool(10);// 固定线程数ExecutorServicepool2Executors.newCachedThreadPool();// 可缓存线程池ExecutorServicepool3Executors.newSingleThreadExecutor();// 单线程ExecutorServicepool4Executors.newScheduledThreadPool(10);// 定时任务线程池看起来很方便但阿里巴巴《Java开发手册》明确规定禁止使用 Executors 创建线程池。为什么因为它们底层调用的还是ThreadPoolExecutor但参数配置有严重隐患方法隐患newFixedThreadPool任务队列是LinkedBlockingQueue容量是Integer.MAX_VALUE相当于无界队列。任务无限堆积会导致OOM。newSingleThreadExecutor同上队列无界会导致OOM。newCachedThreadPool允许创建的线程数是Integer.MAX_VALUE线程无限创建会导致OOM。newScheduledThreadPool同上允许线程数无上限会导致OOM。正确做法始终使用new ThreadPoolExecutor(...)手动指定参数尤其是队列一定要有界// ✅ 正确示范手动创建队列有界ThreadPoolExecutorpoolnewThreadPoolExecutor(4,8,60,TimeUnit.SECONDS,newArrayBlockingQueue(1000),// 有界队列newThreadPoolExecutor.CallerRunsPolicy());写在最后线程池是多线程编程的标配。记住这几个核心结论线程池的核心价值复用线程减少创建/销毁的开销控制并发数量ThreadPoolExecutor 有 7 个参数重点是核心线程数、最大线程数、任务队列和拒绝策略任务队列一定要用有界队列防止 OOM拒绝策略推荐CallerRunsPolicy既能限流又不丢任务不要使用Executors快捷方法它们的隐藏风险在生产环境中极其危险。

相关新闻