除了①线程池,使用线程还有三种方式,分别是 ②继承Thread类 ③实现Runnable接口④实现Callable接口,这三种方式最后都需要新建和销毁线程。在实际的高并发场景下,往往线程数多,每个线程执行任务耗时短。上面三种新建线程的方式,新建和销毁线程的时间消耗经常会比实际执行任务的耗时还要长,导致提交的任务不能立即响应执行,并且新建和销毁线程往往有一定的资源消耗;其次创建新线程的方式线程缺乏统一管理,容易出现线程阻塞的情况。所以这里我们引入了一种复用线程执行任务的方式-线程池,减少新建线程的次数。
首先说说“任务”的概念,实现Runnable或Callable接口创建的对象只能当做一个可以在线程中运行的任务,不是真正意义上的线程,所以最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
参数 | 意义 | 说明 |
---|---|---|
corePoolSize | 核心线程数 | 默认情况,核心线程会一直存活,包括空闲状态 |
maximumPoolSize | (能容纳)最大线程数 | 当已提交的任务数大于等于该值,后面新提交的任务会使用拒绝策略handler |
keepAliveTime | 非核心线程闲时存活期 | 超过该时长,处于闲置状态的非核心线程会被回收(当allowCoreThreadTimeOut(true)时,keepAliveTime也适用于核心线程) |
unit | keepAliveTime单位 | TimeUnit.DAYS; TimeUnit.HOURS;TimeUnit.MINUTES; TimeUnit.SECONDS;TimeUnit.MILLISECONDS;TimeUnit.MICROSECONDS; |
workQueue | 任务队列 | 当corePoolSize个核心线程都运行时,新来的任务就放到这个队列里等待执行(就绪状态) |
threadFactor | 线程工厂接口 | 为线程池创建新线程 |
**ThreadPoolExecutor
**类是线程池中最重要的类,可以通过在该类实例化时配置不同的参数传入构造器,来实现自定义线程池;
// 创建线程池对象,通过 构造方法 配置核心参数
Executor executor = new ThreadPoolExecutor( CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue,sThreadFactory );
// 构造函数
public ThreadPoolExecutor (int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable workQueue>,
ThreadFactory threadFactory )
补充说明:
对于线程池、核心线程、非核心线程自己的通俗理解:
线程池看成一个公司,核心线程可以看成是看成是公司的正式员工,非核心线程可以看成是实习生;无论是正式员工还是实习生在同一时刻只能执行一个任务。当有新任务时,老板首先找正式员工干活;如果正式员工都在干活时继续来新任务,那么多余任务就放到一个任务队列里,等有正式员工闲下来后接着干新任务;如果任务较多,超出正式员工能处理的数量,这时老板就考虑招几个实习生来干活,实习生处理任务队列满了之后新来的任务。如果任务太多了,那么老板就不再接单了,拒绝处理新来的任务。
一般情况,正式员工有任务到来就干活,没任务就空闲,不会被裁;实习生有任务到来就干活,没任务就空闲,空闲的时间太长了,老板不养闲人,就把这个实习生裁了。
// 1.创建线程池,通过配置核心参数,从而实现自定义线程池
Executor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);
// 注:在Java中,已内置4种常用线程池,下面会详细说明
// 2.向线程池提交任务:execute(),传入Runnable对象
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行任务
}
});
// 3. 关闭线程池shutdown()
threadPool.shutdown();
shutdown()
和shutdownNow()
区别:
shutdown()
:等待正在执行任务的线程执行完再关闭线程池;shutdownNow()
:不等待立即关闭;根据参数的不同配置,Java内置了4种常用线程池,他们的参数已经配置好了:
FixedThreadPool
特点:只有核心线程 & 不会被回收、线程数固定、任务队列无大小限制;
应用场景:控制线程池最大并发数;
//创建 定长线程池 对象 & 设置线程池线程数固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//向线程池提交任务:execute()
fixedThreadPool.execute(() -> System.out.println("定长线程池->执行任务啦"));
//关闭线程池
fixedThreadPool.shutdown();
ScheduledThreadPool
特点:核心线程数固定、非核心线程数无限制(闲时立刻回收);
应用场景:执行定时 / 周期性 任务
//创建 定时线程池 对象 & 设置线程池核心线程数固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//创建好Runnable类线程对象 & 需执行的任务
Runnable task1 = () -> System.out.println("定时线程池->执行任务啦");
// 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task1, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task1,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
//关闭线程池
scheduledThreadPool.shutdown();
CachedThreadPool
// 创建 可缓存线程池 对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//向线程池提交任务1:execute()
cachedThreadPool.execute(() -> System.out.println("可缓存线程池-执行任务1"));
//向线程池提交任务2:execute();复用线程
cachedThreadPool.execute(() -> System.out.println("可缓存线程池-执行任务2"));
//关闭线程池
cachedThreadPool.shutdown();
SingleThreadExecutor
特点:只有一个核心线程,保证所有任务按顺序在一个线程中执行,不存在线程安全问题;
应用场景:适合单线程任务;不适合必须使用多线程的耗时任务,如数据库操作、文件操作等;
//创建 单例线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//向线程池提交任务:execute()
singleThreadExecutor.execute(() -> System.out.println("单例线程池-执行任务啦"));
//关闭线程池
singleThreadExecutor.shutdown();
线程池类型 | 池内 线程类型 | 池内 线程数 | 处理特点 | 应用场景 | BlockingQueue |
---|---|---|---|---|---|
定长线程池FixedThreadPool |
核心线程 | 核心线程数固定 | ①核心线程不被回收②任务队列大小无限制 | 控制线程池最大并发数 | LinkedBlockingQueue |
定时线程池ScheduledThreadPool |
核心线程,非核心线程, | 核心线程数固定,非核心线程数无限制 | 非核心线程闲时立刻回收(keepAliveTime为0) | 执行定时 / 周期性任务 | DelayedWorkQueue |
缓存线程池CachedThreadPool |
非核心线程 | 非核心线程数无限制 | ①有闲置线程,复用线程;否则新建线程;②一般,提交任务立刻执行③非核心线程闲时灵活回收 | 执行数量多、耗时少任务 | SynchronousQueue |
单例线程池SingleThreadExecutor |
核心线程 | 1个核心线程 | ①所有任务按FIFO顺序在一个线程中执行②不存在线程安全问题 | 适合单线程任务;不适合必须使用多线程的耗时任务 | LinkedBlockingQueue |