有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理。所以他们在使用线程的时候,多数情况下都是new Thread来实现多线程。但是,往往良好的多线程设计大多都是使用线程池来实现的。
为什么要使用线程池
可以降低线程创建和销毁的资源消耗。 可以提高响应速度。线程的创建时间为T1,执行时间T2,销毁时间T3,通过使用线程池,可以免去T1和T3的时间 可以提高线程的可管理性
下图所示为线程池的实现原理:
调用方不断向线程池中提交任务,线程池中的线程不断地从队列中取任务,这是一个典型的生产者-消费者模型。
实战:手写简易线程池
现在来带大家手写一个简单的线程池,让大家更加理解线程池的工作原理
/**
* 自定义线程池
*/
public class ThreadPool {
/** 默认线程池中的线程的数量 */
private static final int WORK_NUM = 5;
/** 默认处理任务的数量 */
private static final int TASK_NUM = 100;
/** 存放任务 */
private final BlockingQueue<Runnable> taskQueue;
private final Set<WorkThread> workThreads;//保存线程的集合
private int workNumber;//线程数量
private int taskNumber;//任务数量
public ThreadPool(){
this(WORK_NUM , TASK_NUM);
}
public ThreadPool(int workNumber , int taskNumber) {
if (taskNumber<=0){
taskNumber = TASK_NUM;
}
if (workNumber<=0){
workNumber = WORK_NUM;
}
this.taskQueue = new ArrayBlockingQueue<Runnable>(taskNumber);
this.workNumber = workNumber;
this.taskNumber = taskNumber;
workThreads = new HashSet<>();
//工作线程准备好了
//启动一定数量的线程数,从队列中获取任务处理
for (int i=0;i<workNumber;i++) {
WorkThread workThread = new WorkThread("thead_"+i);
workThread.start();
workThreads.add(workThread);
}
}
/**
* 线程池执行任务的方法,其实就是往BlockingQueue中添加元素
* @param task
*/
public void execute(Runnable task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 销毁线程池
*/
public void destroy(){
System.out.println("ready close pool...");
for (WorkThread workThread : workThreads) {
workThread.stopWorker();
workThread = null;//help gc
}
workThreads.clear();
}
/** 内部类,工作线程的实现 */
private class WorkThread extends Thread{
public WorkThread(String name){
super();
setName(name);
}
@Override
public void run() {
while (!interrupted()) {
try {
Runnable runnable = taskQueue.take();//获取任务
if (runnable !=null) {
System.out.println(getName()+" ready execute:"+runnable.toString());
runnable.run();//执行任务
}
runnable = null;//help gc
} catch (Exception e) {
interrupt();
e.printStackTrace();
}
}
}
public void stopWorker(){
interrupt();
}
}
}
上面代码定义了默认的线程数量和默认处理任务数量,同时用户也可以自定义线程数量和处理任务数量。
我们使用BlockingQueue
阻塞队列(之后会具体介绍)来存放任务。用set
来存放工作线程;
构造方法中new对象的时候,循环启动线程,并把线程放入set
中。
通过实现实现Thread
来创建WorkThread
类;
因为有一个stop
方法,所以这里需要while
判断,之后从taskQueue
队列中获取任务。获取不到就阻塞,获取到的话runnable.run();
就执行任务,之后把任务变成null;
销毁线程只需要遍历set
,把每个线程停止,并且变为null
;
执行线程任务execute
。
我们来测试一下:
public class TestMySelfThreadPool {
private static final int TASK_NUM = 50;//任务的个数
public static void main(String[] args) {
ThreadPool myPool = new ThreadPool(3,50);
for (int i=0;i<TASK_NUM;i++) {
myPool.execute(new MyTask("task_"+i));
}
}
static class MyTask implements Runnable{
private String name;
public MyTask(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("task :"+name+" end...");
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name = "+name;
}
}
}
结果ok,没什么问题。