文中技术点,是在JDK1.8环境下运行测试的,其它环境未必能完美运行
Java线程池
核心原理
在Java线程池中最核心的类就是ThreadPoolExecutor,而在ThreadPoolExecutor类中最核心的构造方法就是带有7个参数的构造方法,如下所示(此处借鉴互联网图片):
其中:
- corePoolSize:线程池中的常驻核心线程数。
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1。
- keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。
- unit:keepAliveTime的单位。
- workQueue:任务队列,被提交但尚未被执行的任务。
- threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可。
- handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数(maxnumPoolSize)时,如何来拒绝请求执行的runnable的策略。
并且Java的线程池是通过 生产者-消费者模式 实现的,线程池的使用方是生产者,而线程池本身就是消费者。其核心工作流程如下图所示:
直接手撸
手动实现的线程池要比Java自身的线程池简单的多,可以去掉了各种复杂的处理方式,保留了最核心的原理:线程池的使用者向任务队列中添加任务,而线程池本身从任务队列中消费任务并执行任务。
理解了这个核心原理,接下来的代码就简单多了。在实现这个简单的线程池时,我们可以将整个实现过程进行拆解。拆解后的实现流程为:定义核心、创建内部类RunThread、创建ThreadPool类的构造方法和创建执行任务的方法。
以下代码中,有些变量都是我自定义命名的,请按照个人习惯即可
定义核心ThreadPool类
创建一个名称为ThreadPool的Java类,并在这个类中定义几个常用的核心变量:
- QUEUE_SIZE:静态常量,表示默认的阻塞队列大小。
- runQueue:模拟实际的线程池使用阻塞队列来实现生产者-消费者模式。
- runThreads:模拟实际的线程池使用List集合保存线程池内部的工作线程。
代码片段如下:
//默认阻塞队列大小
private static final int QUEUE_SIZE = 5;
//模拟实际的线程池使用阻塞队列来实现生产者-消费者模式
private BlockingQueue<Runnable> runQueue;
//模拟实际的线程池使用List集合保存线程池内部的工作线程
private List<RunThread> runThreads = new ArrayList<RunThread>();
创建内部类RunThread
在上面那个ThreadPool中创建一个内部类RunThread,模拟线程池中的工作线程。主要的作用就是消费runQueue中的任务,并执行任务。由于工作线程需要不断从runQueue中获取任务,所以,这里使用了while(true)循环不断尝试消费队列中的任务。
代码片段如下:
//内部类RunThread,模拟线程池中的工作线程
//主要的作用就是消费runkQueue中的任务,并执行
//由于工作线程需要不断从runkQueue中获取任务,使用了while(true)循环不断尝试消费队列中的任务
class RunThread extends Thread{
@Override
public void run() {
//不断循环获取队列中的任务
while (true){
//当没有任务时,会阻塞
try {
Runnable runTask = runQueue.take();
runTask.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
创建ThreadPool类的构造方法
ThreadPool类创建两个构造方法,一个构造方法中传入线程池的容量大小和阻塞队列,另一个构造方法中只传入线程池的容量大小。
代码片段如下:
//在ThreadPool的构造方法中传入线程池的大小和阻塞队列
public ThreadPool(int poolSize, BlockingQueue<Runnable> runQueue){
this.runQueue = runQueue;
//创建poolSize个工作线程并将其加入到runThreads集合中
IntStream.range(0, poolSize).forEach((i) -> {
RunThread runThread = new RunThread();
runThread.start();
runThreads.add(runThread);
});
}
//在ThreadPool的构造方法中传入线程池的大小
public ThreadPool(int poolSize){
this(poolSize, new LinkedBlockingQueue<>(QUEUE_SIZE));
}
创建执行任务的方法
在ThreadPool类中创建执行任务的方法execute(),execute()方法的实现比较简单,就是将方法接收到的Runnable任务加入到runQueue队列中。
代码片段如下:
//通过线程池执行任务
public void execute(Runnable task){
try {
runQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
最后放出完整源码ThreadPool类
package com.wlee.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.IntStream;
public class ThreadPool {
//默认阻塞队列大小
private static final int QUEUE_SIZE = 5;
//模拟实际的线程池使用阻塞队列来实现生产者-消费者模式
private BlockingQueue<Runnable> runQueue;
//模拟实际的线程池使用List集合保存线程池内部的工作线程
private List<RunThread> runThreads = new ArrayList<RunThread>();
//在ThreadPool的构造方法中传入线程池的大小和阻塞队列
public ThreadPool(int poolSize, BlockingQueue<Runnable> runQueue){
this.runQueue = runQueue;
//创建poolSize个工作线程并将其加入到runThreads集合中
IntStream.range(0, poolSize).forEach((i) -> {
RunThread runThread = new RunThread();
runThread.start();
runThreads.add(runThread);
});
}
//在ThreadPool的构造方法中传入线程池的大小
public ThreadPool(int poolSize){
this(poolSize, new LinkedBlockingQueue<>(QUEUE_SIZE));
}
//内部类RunThread,模拟线程池中的工作线程
//主要的作用就是消费runkQueue中的任务,并执行
//由于工作线程需要不断从runkQueue中获取任务,使用了while(true)循环不断尝试消费队列中的任务
class RunThread extends Thread{
@Override
public void run() {
//不断循环获取队列中的任务
while (true){
//当没有任务时,会阻塞
try {
Runnable runTask = runQueue.take();
runTask.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//通过线程池执行任务
public void execute(Runnable task){
try {
runQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
编写测试程序
这里就直接用过main方法中调用ThreadPool类的构造方法,传入线程池的大小,创建一个ThreadPool类的实例,然后循环10次调用ThreadPool类的execute()方法,向线程池中提交的任务为:打印当前线程的名称--->> 你好 ThreadPool
。
package com.wlee.test;
import java.util.stream.IntStream;
public class RunPool {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(10);
IntStream.range(0, 10).forEach((i) -> {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "--->> 你好 ThreadPool");
});
});
}
}
执行main方法之后,将输出(其中,当前线程的名称是随机的,也就是说你测试输出的不一定和我的一样):
Thread-1--->> 你好 ThreadPool
Thread-1--->> 你好 ThreadPool
Thread-1--->> 你好 ThreadPool
Thread-1--->> 你好 ThreadPool
Thread-1--->> 你好 ThreadPool
Thread-0--->> 你好 ThreadPool
Thread-2--->> 你好 ThreadPool
Thread-2--->> 你好 ThreadPool
Thread-2--->> 你好 ThreadPool
Thread-4--->> 你好 ThreadPool
至此,我们自定义的Java线程池就开发完成了。
总结
线程池的核心原理其实并不复杂,示例只是示例,还需要结合项目要求,优化使用。