这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
WangScaler: 一个用心创作的作者。
声明:才疏学浅,如有错误,恳请指正。
为了减少我们创建以及销毁线程对内存的消耗,采用线程池来使线程得到复用。最常见的线程池类型有以下几种:
-
newFixedThreadPool:固定数量的线程池。
-
newSingleThreadExecutor:单线程池。
-
newCachedThreadPool:可缓存线程池。
-
newScheduledThreadPool:大小无限制的线程池,可以定时和周期性的执行线程。
-
newWorkStealingPool:java8新增的线程池,它会通过工作窃取的方式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行。
简单使用
newFixedThreadPool
固定数量的线程池,我们这里的参数是3,也就是说有三个线程可以执行任务,每提交一个任务就是一个线程,当任务数达到线程数量,新的任务就进入等待队列。
我们有五个任务,所以当三个线程执行三个任务的时候,剩下的两个任务进入等待,等其中一个线程执行完毕,再执行剩下的任务。
package com.wangscaler.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author WangScaler
* @date 2021/8/23 14:00
*/
public class ThreadPool {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
try {
for (int i = 0; i < 5; i++) {
pool.execute(() -> System.out.println(Thread.currentThread().getName())
);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
执行结果如下:
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1
适合用于执行长期的任务。
newSingleThreadExecutor
单一线程的线程池,所以从始至终都只有一个线程执行任务。
将上述的代码ExecutorService pool = Executors.newFixedThreadPool(3); 替换成ExecutorService pool = Executors.newSingleThreadExecutor();执行结果如下:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
从始至终都只是线程1在执行任务,从而证实了这是个单线程的线程池。适合用于一个任务一个任务执行的场景。
newCachedThreadPool
当线程池的数量大于任务数的时候,就回收部分空闲线程(60s无执行的线程),当任务数增加的时候,再创建新的线程执行任务。
将上述的代码Executors.newFixedThreadPool(3);替换成Executors.newCachedThreadPool();
执行结果如下:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
新建了五个线程执行任务,那么如果我们给上述的线程任务加上休眠之后,会创建几个线程执行任务呢?
package com.wangscaler.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author WangScaler
* @date 2021/8/23 14:00
*/
public class ThreadPool {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 5; i++) {
pool.execute(() -> System.out.println(Thread.currentThread().getName())
);
TimeUnit.SECONDS.sleep(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
再次执行上述的代码,发现和上次的结果不一样了。
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
这就是可缓存线程池,根据任务数动态的管理线程数。适合用于执行很多短期异步的小程序或者负载较轻的服务器。
上述的方式只是演示使用,真正开发时不建议这样创建线程池,到底如何创建?将在下篇文章来讲解。
七大参数
-
1、corePoolSize:核心线程数,当任务达到核心线程数之后,将进入任务队列workQueue进行等待。
-
2、maximumPoolSize:最大线程数即线程池能够容纳同时执行的最大线程数,注意:此值必须大于等于1。
-
3、keepAliveTime:多余的空闲线程存活时间。当前线程池数量超过corePoolSize时,当空闲时间到达keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
-
4、unit:keepAliveTime的时间单位。
-
5、workQueue:任务队列,被提交但尚未执行的任务。
-
6、threadFactory:表示生成线程池中的工作线程的线程工厂,用于创建线程,一般为默认线程工厂即可。
-
7、handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝来请求的Runnable的策略。
执行过程
-
1、创建完线程池之后,等待任务。
-
2、当调用execute()。
-
如果正在运行的线程数小于corePoolSize核心线程数,则创建线程执行任务
-
如果正在运行的线程数不小于corePoolSize核心线程数,且workQueue任务队列未满,则将任务加入workQueue任务队列。
-
如果任务队列也满了,且此时正在运行的线程数小于maximumPoolSize最大线程数,则立即创建新的线程执行这个任务。(注意:这里执行的任务队列以后的新任务,而不是将任务队列的任务执行和新任务加入任务队列)。
-
如果此时正在运行的线程数也不小于maximumPoolSize最大线程数了,则线程池将启用handler饱和拒绝策略。
-
-
3、当线程执行完任务,则将从workQueue任务队列获取新任务继续执行。
-
4、如果线程处于空闲期(即无任务执行)
-
此时线程池的线程数大于corePoolSize核心线程数,则将超过keepAliveTime空闲存活时间的线程注销回收。
-
此时线程池的线程数不大于核心线程数,则不处理。
-
整个流程图大致如下:非专业作图,勿喷。
今天先写到这里,明天从源码的角度看看线程的执行过程。
来都来了,点个赞再走呗!
关注WangScaler,祝你升职、加薪、不提桶!