章
目
录
在讲解Java创建线程池的几种方式之前,潘老师先带大家了解一些关于线程池的概念和原理。
一、Java线程池是什么
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务,并且线程池中的线程都是后台线程。
二、Java线程池作用意义
第1:降低资源消耗。通过重复利用机制已降低线程创建和销毁造成的消耗。
第2:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第3:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
三、Java四种线程池的使用
JDK1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。Java通过Executors为我们提供了四种线程池的创建方式,分别为:
1)newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
// 可缓存线程池 Executors表示启动线程的 可创建线程数是无限大小的 ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int temp = i; // 可执行线程 execute 启动线程 executorService.execute(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "," + temp); } }); } //停止线程池 executorService.shutdown();
2)newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
//创建可固定长度的线程池,只会创建5个线程池进行处理 ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 20; i++) { final int temp = i; // 可执行线程 execute 启动线程 executorService.execute(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "," + temp); } }); } //停止线程池 executorService.shutdown();
3)newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
//创建可定时执行的线程池 数量为3 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { final int temp=i; //schedule 方法表示线程执行 表示延迟5秒之后 开始执行线程 scheduledExecutorService.schedule(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()+""+temp); } }, 5, TimeUnit.SECONDS); } //停止线程池 scheduledExecutorService.shutdown();
4)newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
//创建单线程线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int temp = i; // 可执行线程 execute 启动线程 executorService.execute(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "," + temp); } }); } //停止线程池 executorService.shutdown();
查看Executors
类源码,以newSingleThreadExecutor
方法为例:
我们发现其实都是在使用ThreadPoolExecutor
类进行构建的,然后该类目构造方法有很多参数。这些参数很重要,需要掌握,面试也会经常被问到!
四、ThreadPoolExecutor线程池类7大参数详解
参数 | 含义 |
corePoolSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 线程池所使用的任务缓冲队列 |
threadFactory | 线程工厂,用于创建线程,一般用默认的即可 |
rejectHandler | 线程池拒绝任务时的处理策略 |
五、Java线程池执行流程
一个线程提交到线程池后的具体处理流程如下图所示:
1)如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2)如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
3)如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4)如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
5)当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
总结:处理任务判断的优先级为 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler采取任务拒绝策略进行处理;
线程池的拒绝策略:当最大线程数 + 队列缓存数量 小于线程数量的时候,程序运行出错,被拒绝。(在此不再详细展开)
六、Java自定义线程池
虽然JDK的Executors
提供了4大类线程池供我们直接使用,但是在实际生产开发中,往往是不允许/不建议使用这4种直接调用的方式的,比如在阿里巴巴的Java开发手册中就有如下【强制】要求:
原因就是因为直接创建会容易导致OOM(内存溢出~)
推荐我们手工自定义线程池,在自定义时我们需要预估线程数多少合适,主要参考依据为核心线程数计算公式:
- IO密集型:核心线程数 = CPU核数 / (1-阻塞系数)
- CPU密集型:核心线程数 = CPU核数 + 1
- IO密集型:核心线程数 = CPU核数 * 2
可以创建个工具类如下:
import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolUtil { /** * 默认 CPU 核心数 */ private static int threadPoolSize = 0; static { // 获取服务器 CPU 核心数 threadPoolSize = Runtime.getRuntime().availableProcessors(); System.out.println(" CPU 核心数量:" + threadPoolSize); } public static int getThreadPoolSize() { return threadPoolSize; } /** * 线程工厂,用来创建线程 */ private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-demo-%d").setDaemon(true).build(); /** * 设置线程池核心参数(IO 密集型) 核心线程数 = CPU 核数 * 2 * 参数依次分别为上面的的7大参数,最后拒绝策略参数可以不写,则默认使用AbortPolicy */ private static ThreadPoolExecutor threadPoolExecutorIO = new ThreadPoolExecutor(threadPoolSize, threadPoolSize * 2, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy()); /** * 设置线程池核心参数(CPU 密集型) 核心线程数 = CPU 核数 + 1 // 核心线程数 = CPU 核数 + 1 */ private static ThreadPoolExecutor threadPoolExecutorCPU = new ThreadPoolExecutor(threadPoolSize, threadPoolSize + 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy()); /** * 返回线程池对象 * * @return */ public static ThreadPoolExecutor getThreadPoolExecutorIO() { return threadPoolExecutorIO; } public static ThreadPoolExecutor getThreadPoolExecutorCPU() { return threadPoolExecutorCPU; } }
如果你不想区分CPU密集型还是IO密集型,只是想简单的自己手工创建一个线程池,比如我这里只想创建一个SingleThreadExecutor
,则代码如下:
ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactoryBuilder().setNameFormat("thread-pool-demo-%d").setDaemon(true).build()); for (int i = 0; i < 10; i++) { final int temp = i; // 可执行线程 execute 启动线程 executorService.execute(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "," + temp); } }); }
以上就是Java创建线程池的几种方式具体实现全部内容,如果你有什么疑问,可以评论留言~