当一个任务被提交至线程池,线程池处理任务的流程是:
之所以这样处理,是想要尽可能避免获取全局锁,上面的步骤二无需获得全局锁。线程池在创建线程时,会将线程封装成工作线程worker,worker在执行完任务后,还会从工作队列中获取新的任务来执行。
我们先看一下ThreadPoolExecutor
这个线程池的老祖宗。看看它的构造器:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
创建一个线程池所需要的核心参数如下:
线程池通过遍历线程池中的工作线程,逐个调用线程的interrupt方法中断线程。
shutdown方法会将线程池的状态设置为SHUTDOWN,然后中断没有正在执行任务的线程。是一种委婉的关闭。
shutdownNow会先将线程池状态设置为STOP,然后尝试停止所有正在执行或者暂停任务的线程。是一种强制关闭的策略。
可以从以下角度分析:任务的性质是I/O密集型还是CPU密集型?任务的优先级有区分吗?任务执行时间的长短?任务的依赖性?是否依赖于其他资源(比如数据库连接)?
FixedThreadPool是固定线程数的线程池。有时我们为了满足资源管理的需求,需要限制当前线程数量,比如用于负载较重的服务器。
特点:
它的核心线程出和固定线程数都被设置成了一样的大小,且不为0。但是它的线程存活时间被设置成了0,且使用的是无界阻塞队列(LinkedBlockingQueue)。
这意味着多余的空闲线程会被立即终止,无法复用。且线程池中的线程数不会超过核心线程数,因为无法立即执行的任务都跑到无界队列中去了,当核心线程数小于初始值时,再去队列中poll()任务就可以了。
当然,由于是无界队列,其也不会拒绝任务。
SingleThreadExcutor是使用单个worker线程的Executor。适用于保证顺序地执行各个任务,在任意时间点,不会有多个线程是活动的应用场景。
特点:其核心线程数和最大线程数都被设置为1。也是使用的无界队列(LinkedBlockingQueue)。它的运行机制与特性与FixedThreadPool相似,最大的不同就是此线程池中永远只有一个正在执行任务的线程。
它是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器。
特点:它的核心线程数量被设置为0,最大线程数量被设置为最大值。而线程存活时间为60s,意味着此线程池中空闲线程等待任务的时间为60s。
它使用无容量的SynchoronousQueue作为线程池的工作队列。如此设置,意味着,如果主线程提交任务的速度高于线程池处理任务的速度,则线程池会不断地创建新的线程,极端情况下会耗尽CPU和内存资源。
SynchoronousQueue没有容量,在这里起到什么作用呢?其实如果不设置60s的存活时间,那么SynchoronousQueue意义就不大,效率也不会高,我们在小程序较多的场景中,追求的就是效率,SynchoronousQueue起到一个传递者的作用,如果使用其他的有界或者无界队列,虽然可以达到资源的控制目的,但是在此场景下非常耗费时间,所以我们需要一个控制者,而不是一个容器。试想一下,如果一个小任务很快被执行(poll->excute)完了,还不到60s,此时刚刚所用的线程可以继续复用,而中间的步骤资源开销很少。
设置默认的线程数,如果用户没有设置,就用默认的。
private static int WORK_NUM = 5;
设置默认的任务个数(这里指的是默认的工作队列的大小,使用有界队列)。
private static int TASK_COUNT = 100;
设置存放正在工作线程的容器,这里我们用一个工作线程类型的数组来实现,这个数组主要用于在开启或者关闭线程时,可以遍历到设置的线程。
private WorkThread[] workThreads;
设置尚未工作的线程存放队列容器。这里我们会使用ArrayBlockingQueue。
private final BlockingQueue
构造线程数。用户自定义的线程数。
private final int worker_num
通过继承Thread类的方法,重写run()。
run()主要做的是:如果没有被shutdown()中断,那么会一直轮询,从工作队列中取出任务来执行。执行完之后需要释放掉引用,以免内存溢出。
设置停止线程的方法stopWorker()。直接使用interrupt()即可。
private class WorkThread extends Thread{
@Override
public void run(){
Runnable r = null;
try {
while (!isInterrupted()) {
r = taskQueue.take();
if(r!=null) {
System.out.println(getId()+" ready exec :"+r);
r.run();
}
r = null;//help gc;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void stopWorker() {
interrupt();
}
}
public MyThreadPool2(int worker_num,int taskCount) {
//用户传入的工作线程数如果小于0,就用默认的
if (worker_num<=0) worker_num = WORK_NUM;
//用户传入任务数如果小于0,就用默认的
if(taskCount<=0) taskCount = TASK_COUNT;
this.worker_num = worker_num;
//初始化任务队列容器
taskQueue = new ArrayBlockingQueue<>(taskCount);
//初始化工作线程容器
workThreads = new WorkThread[worker_num];
//创建并开启工作线程容器中的全部线程
for(int i=0;i<worker_num;i++) {
workThreads[i] = new WorkThread();
workThreads[i].start();
}
}
当用户执行execute()的时候,只管往里扔就行,无需显示地调用start()方法。因为线程池会通过轮询自动从工作队列中取出任务去执行。
public void execute(Runnable task) {
try {
taskQueue.put(task);//扔到队列中即可
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void destroy() {
// 工作线程停止工作,且置为null
System.out.println("ready close pool.....");
for(int i=0;i<worker_num;i++) {
workThreads[i].stopWorker();
workThreads[i] = null;//help gc
}
taskQueue.clear();// 清空任务队列
}
package com.xiangxue.ch6.mypool;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPool2 {
// 线程池中默认线程的个数为5
private static int WORK_NUM = 5;
// 队列默认任务个数为100
private static int TASK_COUNT = 100;
// 工作线程组
private WorkThread[] workThreads;
// 任务队列,作为一个缓冲
private final BlockingQueue<Runnable> taskQueue;
private final int worker_num;//用户在构造这个池,希望的启动的线程数
// 创建具有默认线程个数的线程池
public MyThreadPool2() {
this(WORK_NUM,TASK_COUNT);
}
// 创建线程池,worker_num为线程池中工作线程的个数
public MyThreadPool2(int worker_num,int taskCount) {
if (worker_num<=0) worker_num = WORK_NUM;
if(taskCount<=0) taskCount = TASK_COUNT;
this.worker_num = worker_num;
taskQueue = new ArrayBlockingQueue<>(taskCount);
workThreads = new WorkThread[worker_num];
for(int i=0;i<worker_num;i++) {
workThreads[i] = new WorkThread();
workThreads[i].start();
}
}
// 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public void destroy() {
// 工作线程停止工作,且置为null
System.out.println("ready close pool.....");
for(int i=0;i<worker_num;i++) {
workThreads[i].stopWorker();
workThreads[i] = null;//help gc
}
taskQueue.clear();// 清空任务队列
}
// 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数
@Override
public String toString() {
return "WorkThread number:" + worker_num
+ " wait task number:" + taskQueue.size();
}
/**
* 内部类,工作线程
*/
private class WorkThread extends Thread{
@Override
public void run(){
Runnable r = null;
try {
while (!isInterrupted()) {
r = taskQueue.take();
if(r!=null) {
System.out.println(getId()+" ready exec :"+r);
r.run();
}
r = null;//help gc;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void stopWorker() {
interrupt();
}
}
}
可以使用JDK中的Executors工具类获取已经实现的线程池,也可以继承ThreadPoolExecutor类自定义线程池。