对于服务端的应用而言,经常会出现比如定时任务,或者任务的异步执行,熟悉Java开发的开发者,经常会使用Executors类,其提供了4种不同的线程池: newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor 这些线程池为我们在异步任务的执行等多线程的编程下提供了非常大的便利。
试想一下,每个任务都创建一个线程,这是非常不合理的,如果同时有大量的应用执行任务,那么我们的系统估计将会出现故障。通过过多的线程,在进行线程的上下文切换的过程中也是增加了系统的负载,而线程的创建,切换以及销毁都是需要消耗系统的资源的。
基于此,线程池技术应运而生,所谓的线程池技术就是在系统中存在一定数量的预先创建好的线程,这些线程接收保存在集合中的任务,从任务列表中获取任务去执行。执行完成之后,等待任务池中添加新的任务,然后取出,继续执行,如此往复。线程池技术一方面减少了线程池的创建,维护的成本,另一方面也在一定程度上缓解了大量任务导致线程被大量创建的问题。
1、线程池的定义
一般的线程池均有以下的定义(基于演示我们这里不做过多的功能,比如Java提供的线程设置任务池的大小,丢弃策略等等,这些并不是本文的重点,后面会在本栏目的其他文章中专门讲解线程池的实现以及应用)客户端通过将任务添加到任务池中就立即返回,不在等待任务的执行完成,提交任务完成后,其他工作线程接收到有新的任务的通知,会自动的获取任务并执行。
public interface ThreadPool<T extends Job> {// 添加任务到任务池中void execute(T t);// 关闭线程池void shutdown();// 获取等待执行的任务数目int getCount();}
2、简单实现线程池
首先先定义一个抽象的任务类,抽象的任务类,有run方法,用于任务的执行。
public abstract class Job {
/** 执行Job任务 */
public abstract void handle();
/** 返回任务的编号 */
public abstract String number();
}
// 定义的睡眠任务
public class SleepJob extends Job {
private final int number;
SleepJob(int number) {
this.number = number;
}
@Override
public void handle() {
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public String number() {
return String.valueOf(this.number);
}
}
Worker实现了Runable接口,是线程池中的工作者,用于执行线程池中的任务jobs ```java // 工作线程,负责消费任务 public class Worker implements Runnable {
private final DefaultThreadPool pool;
private boolean running;
public Worker(DefaultThreadPool pool) { this.pool = pool; this.running = true; }
@Override public void run() { while (running) {
// 从连接池中获取任务,如果获取到了任务,就执行 Job job = pool.getJob(); if (job != null) { job.handle(); System.out.println("任务:" + job.number() + "执行完成"); }} }
// 关闭该工作线程 public void shutdown() { running = false; System.out.println(“线程:” + Thread.currentThread().getName() + “ 关闭”); }
- 实现线程池的代码, init()方法中初始化若干个Worker,以Worker为基础创建线程并启动,Worker启动后,调用线程池的getJob方法用户获取任务。由于此时没有任务,线程均会执行wait() 工作线程进入WAIT状态,当调用线程池的增加任务时候,线程池执行 `jobs.notify()` 方法,通知并幻想处在WAIT状态的一个线程去获取Job并执行。
```java
import java.util.LinkedList;
public class DefaultThreadPool implements ThreadPool<Job> {
// 工作队列
final LinkedList<Job> jobs = new LinkedList<>();
// 工作线程
LinkedList<Worker> workers = new LinkedList<>();
// 初始化任务
public void init(int initSize) {
if (initSize < 0) {
throw new RuntimeException("xxx");
}
for (int i = 0; i < initSize; i++) {
// 构建Worker并启动线程,worker会等待任务池jobs中添加任务
Worker worker = new Worker(this);
workers.addLast(worker);
Thread workThread = new Thread(worker, "ThreadPool-" + i);
workThread.start();
}
}
@Override
public void shutdown() {
this.workers.forEach(Worker::shutdown);
}
@Override
public int getCount() {
return this.jobs.size();
}
// 获取一个任务
public Job getJob() {
synchronized (jobs) {
while (jobs.isEmpty()) {
try {
jobs.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
return jobs.removeFirst();
}
}
// 添加任务
@Override
public void execute(Job job) {
if (job == null) {
return;
}
// 添加任务,并发出通知唤醒一个线程,任务池有任务,getJob()方法获取到任务并返回
synchronized (jobs) {
jobs.addLast(job);
jobs.notify();
}
}
}
添加一个Job之后调用 jobs.notify() 方法通知一个线程去执行,这里不是调用 jobs.notifyAll() 是为了防止多个线程一起进入同步阻塞队列中。
3、线程池的使用
线程池创建完成之后,使用线程池就很简单了,下面的任务是创建5个工作线程,初始化100个任务交给这5个工作线程执行。
public static void main(String[] args) {
// 初始化线程池
DefaultThreadPool pool = new DefaultThreadPool();
pool.init(5);
System.out.println("ThreadPoolDemo.main start");
// 添加100个任务,等待线程池中的工作线程去执行
for (int i = 0; i < 100; i++) {
pool.execute(new SleepJob(i));
}
}
这个测试案例相对而言比较简单,下面的一篇文章将会实现 基于线程池的简单的Web服务器 ,敬请关注!!!
4、 总结
可以看到,线程池的本质就是使用线程安全的工作队列连接工作线程以及客户端,客户端将任务放置到线程池中返回,工作线程从工作队列中取出任务,执行之。当工作队列为空的时候,全部的工作对垒均处于等待状态(WAITING),当客户端提交工作任务之后,将会通知任意一个线程启动去获取并执行任务。随着大量的任务被添加到工作队列,会有更多的工作线程被唤醒。
