Java实现简单线程池
简介
在这篇博文中,我会实现一个简单的线程池。
线程池的基本要素有:
1.任务队列,负责接收要调度的任务。
2.线程容器,负责创建多个线程,并依据一定的策略调度任务。
3.生命周期方法,线程池中的线程会不断地等待新任务的到来。如果不提供方法明确取消线程的执行,任务执行完后程序不会终止。注意我们如何实现取消线程。
小知识:Java中线程分为普通线程和Deamon线程两种,Java进程在普通线程全部执行完成之前,不会终止。
4.线程池的实现使用了生产者消费者模式。
另外,为了符合大家的习惯,接口设计会参照jdk中相关接口。
Java代码
我会把线程池框架代码和客户端调用代码分离,请大家注意这个细节。
框架代码
首先是外部接口的定义
public interface ExecutorService {
void execute(Runnable task);
void shutDown();
}
任务队列
package org.lin.thread.executor.framework;
import java.util.LinkedList;
import java.util.Queue;
/**
* 任务队列,生产者消费者模式中的Channel角色
* @author linjingfu
*
*/
public class TaskQueue {
private Queue<Runnable> taskList = new LinkedList<>(); //任务队列
private boolean terminated = false; //我们使用synchronized保护可见性
//构造方法包可见性,不希望框架外代码实例化该类
TaskQueue() {}
public synchronized Runnable take() throws InterruptedException {
if (terminated && taskList.isEmpty()) {
return null;
}
while (taskList.isEmpty()) {
wait();
}
Runnable task = taskList.poll();
notifyAll();
return task;
}
public synchronized void put(Runnable task) throws InterruptedException {
while (taskList.size() == Integer.MAX_VALUE) {
wait();
}
taskList.add(task);
notifyAll();
}
public synchronized void setTerminated(boolean terminated) {
this.terminated = terminated;
}
}
工作者线程
package org.lin.thread.executor.framework;
/**
* 工作线程,消费者
* @author linjingfu
*
*/
public class TaskThread extends Thread{
private TaskQueue queue;
public TaskThread(int id, TaskQueue queue) {
this.queue = queue;
setName("pool-thread-No." + id);
}
@Override
public void run() {
try {
while (true) {
Runnable task = queue.take();
if (task == null) {
//取不到,说明已经被shutDown,并且任务执行完成
break;
}
task.run();
}
} catch (InterruptedException e) {
}
}
}
线程数量固定的线程池
package org.lin.thread.executor.framework;
/**
* 线程池对象,任务生产者
* @author linjingfu
*
*/
public class FixedThreadPool implements ExecutorService{
private Thread[] threads; //保留线程引用,以中断线程执行
private TaskQueue queue = new TaskQueue();
FixedThreadPool(int threadNum) {
threads = new Thread[threadNum];
for (int i = 0; i < threadNum; i++) {
threads[i] = new TaskThread(i + 1, queue);
threads[i].start();
}
}
@Override
public void execute(Runnable task) {
if (task == null) {
throw new IllegalArgumentException("task不能为null!");
}
try {
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void shutDown() {
queue.setTerminated(true);
}
}
简单工厂提供获取线程池的接口
package org.lin.thread.executor.framework;
/**
* 简单工厂,生成线程池
* @author linjingfu
*
*/
public class Executors {
public static ExecutorService newFixedThreadPool(int threadNum) {
return new FixedThreadPool(threadNum);
}
}
客户端调用代码
package org.lin.thread.executor;
import java.util.concurrent.atomic.AtomicInteger;
import org.lin.thread.executor.framework.ExecutorService;
import org.lin.thread.executor.framework.Executors;
public class Client {
private static AtomicInteger taskId = new AtomicInteger(0);
public static void main(String[] args) {
//创建自定义线程池
ExecutorService service = Executors.newFixedThreadPool(5);
//使用线程池执行10个任务
for (int i = 0; i < 10; i++) {
service.execute(() -> {
for (int j = 0; j < 100000000; j++) {
//模拟耗时任务
}
System.out.println("在线程 " + Thread.currentThread().getName() + " 中任务" + taskId.incrementAndGet() + "执行完成!");
});
}
//在任务执行完后,终止工作线程
service.shutDown();
}
}
运行结果
总结
上面的代码,都是我看完《图解多线程设计模式》一书的Worker Thread模式后独自编写的,获益良多。
从上面代码中,可以学习多线程编程的几点内容:
1.在类中封装互斥策略,如TaskQueue类使用this锁来进行互斥加锁的操作,其他的类,如FixedThreadPool和TaskThread类在调用TaskQueue的同步方法时,不需要去考虑线程安全问题。
2.使用wait()和notify()实现线程协作。
3.这里我们通过检测标志变量terminated,从而取消工作者线程,同样我们将取消策略封装在了TaskQueue中。
4.这一点不属于多线程编程的内容,但可以看到框架中的一些类,我们将构造函数设为包级可见,只对外提供了ExecutorService接口,以及工厂类Executors,好处是可以防止框架内部的类被误用。