为避免频繁地创建和销毁线程,我们可以复用创建的线程,具体操作为:
创建线程池的目的:避免频繁地创建和销毁线程
为什么要避免频繁地创建和销毁线程?减少开销!
所以我们必须对线程数量、使用过程加以管控,线程池技术就这样应运而生了。
线程池位于 JUCjava.util.concurrent
包
核心线程池 ThreadPoolExecutor
拥有许多关键属性、方法,理解它们对我们学习线程池十分重要,甚至可以尝试设计自己的线程池。
变量ctl:一个32位的AtomicInteger类型的原子对象,记录两个关键信息:
主锁 - mainLock
主锁(mainLock),对关键方法提供 “锁” 支持,避免自身操作线程不安全
源码注释:
The runState provides the main lifecycle control, taking on values:
RUNNING: Accept new tasks and process queued tasks
SHUTDOWN: Don’t accept new tasks, but process queued tasks
STOP: Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks
TIDYING: All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
TERMINATED: terminated() has completed
翻译注释:
英文名 | 中文译名 | 状态解释说明 |
---|---|---|
RUNNING | 【运行态】 | 可接收新任务,且可处理队列中的任务 |
SHUTDOWN | 【关闭态】 | 不再接受新提交的任务,但可以继续处理正在执行的任务和队列中的任务 |
STOP | 【停止态】 | 不再接受新提交的任务,也不处理队列中的任务,中断当前正在执行任务的线程 |
TIDYING | 【休整态 / 等待态】 | 表示所有的任务已执行完毕,workerCount (有效线程数) 为0,但线程池仍未终止 |
TERMINATED | 【终止态】 | 线程池彻底终止运行。 |
线程状态 | 转换描述 | 解释说明 |
---|---|---|
RUNNING (运行态) | - | 初始线程池处于RUNNING状态,此时线程池中的任务为0 |
RUNNING (运行态) → SHUTDOWN (关闭态) | On invocationi of shutdown() | 调用shutdown()方法 |
RUNNING (运行态) / SHUTDOWN (关闭态) → STOP (停止态) | On invocation of shutdownNow() | 调用shutdownNow()方法 |
SHUTDOWN (关闭态) → TIDYING (休整态) | When both queue and pool are empty | 任务队列、线程池均为空 |
STOP (停止态) → TIDYING (休整态) | When pool is empty | 线程池为空 |
TIDYING (休整态) → TERMINATED (终止态) | When the terminated() hook method has completed | terminated方法执行完毕 |
keepAliveTime:线程池线程数量超过corePoolSize时,额外新创建线程(非核心线程)的存活时间(即多长时间会被销毁)
workQueue:任务队列 / 等待队列(workQueue),用于存储被提交但尚未被执行(未被分配线程,如:任务数 > 最大线程数)的任务,方便向任务第一时间分配线程,执行的是FIFIO原则(先进先出)。
ThreadPoolExecutor类最后一个参数指定了拒绝策略,而 RejectedExecutionHandler 是拒绝策略的相关接口,通过实现本接口,可以创建自己的拒绝策略。
拒绝策略,即当任务数量超过系统实际承载能力时,采取的补救措施。
execute方法负责任务的分配工作:
创建一个可缓存线程池,如有需要,可灵活回收空闲线程,若无可回收,则新建线程。
先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
参数 | 解释说明 |
---|---|
corePoolSize(核心线程数) | 0 |
maximumPoolSize(最大线程数) | Integer.MAX_VALUE(过量创建可能会导致 Out of Memory) |
keepAliveTime(非核心线程结束后存活时间)、unit(时间单位) | 60秒 |
workQueue(任务队列) | SynchronousQueue 无缓冲等待队列 |
public static void main(String[] args) {
// 创建一个可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName() + "正在被执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
参数 | 解释说明 |
---|---|
corePoolSize(核心线程数) | 传入参数 |
maximumPoolSize(最大线程数) | 传入参数 |
keepAliveTime(非核心线程结束后存活时间)、unit(时间单位) | 0毫秒 |
workQueue(任务队列) | LinkedBlockQueue 无界缓存任务队列 |
public static void main(String[] args) {
// 创建一个可重用固定个数的线程池(定长为3)
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName() + "正在被执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
创建一个定长线程池,支持定时及周期性任务执行。
参数 | 解释说明 |
---|---|
corePoolSize(核心线程数) | 传入参数 |
maximumPoolSize(最大线程数) | Integer.MAX_VALUE(过量创建可能会导致 Out of Memory) |
keepAliveTime(非核心线程结束后存活时间)、unit(时间单位) | 0毫秒 |
workQueue(任务队列) | DelayedWorkQueue 迟滞任务队列 |
单参数方法:
public static void main(String[] args) {
//创建一个定长线程池,支持定时及周期性任务执行——延迟执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//延迟1秒后每3秒执行一次
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟1秒后每3秒执行一次");
}
}, 1, 3, TimeUnit.SECONDS);
}
创建一个单线程化的线程池,它只会用唯一的工作线程(串行)来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
参数 | 解释说明 |
---|---|
corePoolSize(核心线程数) | 1 |
maximumPoolSize(最大线程数) | 1 |
keepAliveTime(非核心线程结束后存活时间)、unit(时间单位) | 0毫秒 |
workQueue(任务队列) | LinkedBlockQueue 无界缓存任务队列 |
无参数方法:
public static void main(String[] args) {
//创建一个单线程化的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
//结果依次输出,相当于顺序执行各个任务
System.out.println(Thread.currentThread().getName() + "正在被执行,打印的值是:" + index);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
创建一个单线程化的线程池,它只会用唯一的工作线程(串行)来执行任务,支持定时及周期性任务执行。
参数 | 解释说明 |
---|---|
corePoolSize(核心线程数) | 1 |
maximumPoolSize(最大线程数) | 1 |
keepAliveTime(非核心线程结束后存活时间)、unit(时间单位) | 0毫秒 |
workQueue(任务队列) | DelayedWorkQueue 迟滞任务队列 |
无参数方法:
可指定工厂的方法:
周期性单任务线程池即定长为1的周期性定长线程池,这里我们不做测试。
Java中获取CPU数量的代码:
Runtime.getRuntime().availableProcessors();
优化线程池大小所需要考虑的因素:
估算线程池大小公式:
Ncpu = CPU数量
Ucpu = 目标CPU的使用率(0 ≤ Ucpu ≤ 1)
W/C = 等待时间 / 计算时间
最优线程池大小:
Nthread = Ncpu × Ucpu × (1 + W/C)
“分而治之” 一直是一个非常有效地处理大量数据的方法。
假设我们需处理1000个数据,但并不具备这种能力,我们可以只处理10个,再分阶段处理100次,最终合成即可得到想要的结果。
在 Linux 中,方法 fork() 用来创建子进程,使得系统进程可以多执行一个分支。Java线程也采取了类似的命名。
方法 | 作用 |
---|---|
fork | 开启线程 |
join | 等待 |
我们不能毫无顾忌地使用 fork() 方法开启线程进行处理,可能会因开启过多的线程而严重影响性能。
在JDK中,给出了ForkJoinPool线程池,我们可以将线程提交给它进行处理,节省资源。
我们向ForkJoinPool 线程池提交 ForkJoinTask 任务,它支持 fork() 方法分解、 join() 方法等待的任务。ForkJoinTask
为模板类,它实现了 Future接口
,其下辖两个子类RecursiveTask
(返回V类型)、RecursiveAction
(无返回值),这三个类均为抽象类。
全参数构造方法:
对于两种构造方法,无特殊需求,一般使用无参构造方法,全参数构造方法作为了解即可。
利用 Fork/Join 框架计算数列(等差数列:差值为1)求和:
public class CountTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 10000; //任务不分割阈值
private long start; //起点
private long end; //终点
/**
* 构造方法初始化变量
* @param start 起点
* @param end 终点
*/
public CountTask(long start, long end) {
this.start = start;
this.end = end;
}
/**
* 重写 compute()方法
* @return 返回子任务结果
*/
@Override
protected Long compute() {
long sum = 0; //记录总结果
boolean canCompute = (end - start) < THRESHOLD; //小于阈值可以直接运算,否则分割任务
if (canCompute) {
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
long step = (start + end) / 100; //分割为100个子任务
ArrayList<CountTask> subTasks = new ArrayList<>(); //存储各个子任务
long pos = start;
for (int i = 0; i < 100; i++) {
long lastOne = pos + step;
if (lastOne > end) lastOne = end;
CountTask subTask = new CountTask(pos, lastOne);
pos += step + 1;
subTasks.add(subTask); //添加子任务
subTask.fork(); //执行子任务
}
for (CountTask task : subTasks) {
sum += task.join(); //将子任务结果并入总结果
}
}
return sum; //返回总结果
}
}
在main方法中创建对象并调用函数,计算0~300000等差数列的和:
//在main方法中创建对象并调用函数,计算0~300000等差数列的和
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask task = new CountTask(0, 300000L);
ForkJoinTask<Long> result = forkJoinPool.submit(task);
try {
long res = result.get();
System.out.println("总和:" + res);
} catch (Exception e) {
e.printStackTrace();
}
}
newWorkStealingPool适合使用用于解决耗时操作的问题,newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,能够较为合理地分配CPU资源,它是一种具有抢占式操作的线程池。
//获取可用CPU数量
Runtime.getRuntime().availableProcessors();