在上一篇文章中《java线程池学习(一) —— BlockingQueue》,我们简单探讨了一下BlockingQueue的概念。
那么在这边文章,我们要利用BlockingQueue来自己实现一个简单的线程池,在以后的章节中,我们再学习一下怎么去使用java为我们封装好的线程池。
首先我们关注一个 “生产者消费者” 的情景。
生产者:不断产生新的需要解决的任务,比如查询数据库,执行某些业务逻辑等。
消费者:不断解决产生的问题。
那么把这两者连接起来的就要用到我们的BlockingQueue了。
生产者将不断产生的任务放入到队列中,如果队列满了,生产者等待。
消费者不断的从队列中取出任务解决,当队列空了,消费者等待新任务到来。
首先BlockQueue的长度我们要限制,不然如果解决者的解决能力跟不上生产者的,这个任务队列就会越来越多。
接着我们还需要限定问题解决者的个数,就是我们所谓的线程池中能同时运行的最多的线程数,如果线程数太多的话会严重影响系统的稳定性。
那么我们根据这两个参数写一个简单的线程池:
线程池:
public class ThreadPool {
//用blockingQueue创建一个任务队列,初始化长度为5
private BlockingQueue<Runnable> tasksQueue = new ArrayBlockingQueue<Runnable>(5);
//定义线程池中消费者最大数量
private int consumers = 3;
//这个方法提供给所有的任务生产者,产生新的任务插入
public void insertTask(Runnable task) throws InterruptedException{
tasksQueue.put(task);
}
//线程池的初始化
ThreadPool(){
//激活消费者,等待问题到来
for(int i=1;i<=consumers;i++){
Solver consumer = new Solver(tasksQueue,i);
consumer.start();
}
}
}
接下来定义 消费者 逻辑:
public class Solver extends Thread{
//引用线程池的任务队列,消费者不断的从里面取得任务去解决
private BlockingQueue<Runnable> taskQueue = null;
String name;
Solver(BlockingQueue<Runnable> tasks,int name){
this.taskQueue = tasks;
this.name = String.valueOf(name);
}
public void run(){
try {
while(true){
//从队列中取出任务执行,注意这里用了take方法,所以如果队列空了,那么线程会等待,直到有任务来了,继续执行
Runnable task = taskQueue.take();
System.out.println("消费者"+name+"接收了一个任务");
task.run();
System.out.println("消费者"+name+"解决了一个任务");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们在上面的例子可以看到。
这个线程池中最大的线程数是3,就是最多只能同时有3个消费者线程执行,消费者会监视线程池的任务队列,只要队列中有任务,就会取出来执行。
接下来我们定义 生产者 的逻辑:
public class ProblemCreater {
public static void main(String[] args) throws Exception {
//初始化线程池
ThreadPool threadPool = new ThreadPool();
//生成者不断产生任务
for(int i=1;i<10;i++){
//定义一个新的任务
Runnable task = new Runnable(){
public void run(){
Random random = new Random();
//随机一个数字模拟需要解决的时间
int randomTime = Math.abs(random.nextInt())%20;
//System.out.println("这个任务需要解决时间为:"+randomTime);
try {
Thread.sleep(randomTime*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//将问题插入到线程池任务队列中
threadPool.insertTask(task);
System.out.println("插入新的任务"+i);
}
}
}
到此我们已经实现好了一个非常简单的线程池,将线程的创建与执行过程分离开,而不是将线程的生命周期管理和任务的执行过程绑定在一起,如果只是想简单的丢一个任务进去执行,我们只需要将任务的执行过程封装到一个Runnable接口中就可以了。而对于那些需要返回结果的任务,我们可以将其封装到Callable接口里面。
当然java线程池比我们自己写的这个高大上很多,在以后的篇幅中,我们再做具体研究。