参考资料:
http://developer.51cto.com/art/201202/314316.htm
所谓的线程池:
-
预创建一些默认数量的线程;
-
当有新任务到来时,直接调用线程池中的线程来完成任务;
-
创建线程的时间t1,销毁线程的时间t3,而线程真正做事的时间为t2;
那么只有在t2<t1+t3的情况下,使用线程池技术才是合理的;
类似于对象池,线程池要完成的功能也类似:
-
创建线程池;
-
销毁线程池;
-
添加任务:获取线程,返回线程;
此处要考虑的几个关键点:
-
对象池中我们采用vector数据结构来组织所有的对象,那么线程池中,我们采用什么数据结构来组织对象?
后续的处理过程要求任务以特定的方式组织,如果考虑任务采用先到先执行的处理方式,那么就可以考虑将任务组织成队列;新到的任务到队列头,执行任务时只要队列头取一个任务即可。
-
线程的行为:有任务的时候,执行任务;而没任务的时候,线程应该处于阻塞状态;
从线程的行为可以推得:
-
线程内部包含循环;b. 在没有任务时,线程应该处于睡眠状态;
-
要考虑线程与任务的绑定:
简单地说:如何让线程来执行任务?如果所有的任务都实现一个统一的接口,这样的话,线程中就可以通过该接口来执行任务。此处我们让所有的任务实现runnable接口,相应的代码应该类似如下:
run(){
runnable r= null;
while(isRunning){
run = taskQueue().get();
r.run();
}
}
Class taskextends runnable{
public void run(){
}
}
-
还需要注意同步的问题:taskqueue的操作需要保证串行特性,保证添加或者取出操作时的不相互干扰。所以在对taskqueue处理的时候需要增加对象锁,从而保证一致性。
线程池的实现
线程与任务主要的交互通过队列来实现:
线程在没有任务的时候,就出于睡眠状态;一旦有任务了,线程就执行任务;对比对象池,需要为每个对象的状态进行设定;而线程池中不存在这样的问题;
线程的状态:通过自己去查找taskqueue来改变。有任务那就进入执行任务状态;否则,线程就出于停顿等待过程;
1.对于线程池中的线程,命名为PooledThread.java,主要包含的操作:
-
访问任务队列,如果有新的任务就开始执行任务;
-
否则,当前的线程处于wait()状态;
-
主要的数据结构为:runnable接口变量,用来执行任务;
由于taskqueue是线程与任务之间的通道,所以taskqueue的设计是难点:
taskqueue的定义,涉及到runnable接口,本文中采用的是linkedList,整个的定义如下:
List<runnable>taskqueue = new LinkedList<runnable>();
那么要往其中增加对象的调用:
Taskqueue.add(t); //t为task对象;
那么从taskqueue中取出一个对象可以使用:
Taskqueue.remove(0);
如果要将整个的taskqueue中的任务都删除的话:
可以使用如下的命令:taskqueue.clear();
2. 线程池中的创建线程池和销毁线程池方法都是需要的;
那么创建线程池时,createPool()需要根据线程池指定数量的工作线程数,创建相应数量的线程;并且将线程启动于开工状态;
而销毁线程时,也需要注意当前是否所有的线程都已经处于空闲状态;如果不是空闲的话,需要等待直到所有的线程都处于空闲(此处如何做到?);然后再将线程池销毁;所谓的空闲,也只要任务队列为空即可。
而销毁线程只要让工作线程退出循环状态即可。
-
与对象池对比,线程池中的线程是不需要回收的;但可以考虑动态地增加,如果当前线程池中的线程不够执行当前的所有任务,可以考虑动态扩展线程池中的线程;
-
任务对列中的增加与删除;对于任务队列中的任务删除,其实由工作线程调取任务队列中的任务就已经完成;
往任务队列中增加任务,这需要有特定的接口,假设我们将其中你的接口定名为:execute;
其中主要也是调用taskqueue.add()方法;该操作与taskqueue.remove()互斥,故使用taskqueue对象锁;
该思考的问题都都考虑到了,可以试着去实现下看看。