线程池的作用是充分利用CPU资源,减小线程创建、注销的系统开销。线程池一般应该包括四个部分:
1、线程池管理器
2、任务队列
3、任务接口
4、工作线程
线程池是一个典型的生产者-消费者模式的应用,可以充分利用java中的多线程API。
任务的分配有两种方式:
1、由管理器分配,这时资源的同步需要由管理器实现;
2、工作线程主动获取任务,这时资源的同步需要由任务队列实现;
本例采用第二种方式。
一、一个简单的例子
1、线程池管理器
对线程池进行统一管理,应该维护一个工作线程的列表、一个任务队列。
至少对外开放一个运行接口,供用户调用来启动线程池。也可以有一个停止方法stop(),本例中没有写。
/*
* 线程池管理器
*/
public class PoolManager {
private MyQueue q; //任务队列
private Worker [] ths; //线程数组
public PoolManager(int workerNum, MyQueue q){
ths = new Worker[workerNum];
this.q = q;
}
//启动线程池
public void execute(){
for(int i=0; i<ths.length; i++){
Worker w = new Worker(i, q);
ths[i] = w;
}
for(int i=0; i<ths.length; i++){
ths[i].start();
}
}
}
2、任务队列
本例中,任务队列是一个先进先出的数据结构,当然可以按照需求采用其他的数据结构。
任务队列用来保存用户的工作任务,属于生产者-消费者里面的产品角色。至少应该对外开放两个接口:任务入队列、任务出队列,并实现方法同步。
/*
* 任务队列
*/
public class MyQueue {
private IJob[] jobArray; //采用数组的方式存储,也可以用链表等方式
private int front; //队列头
private int tail; //队列尾
private int size;
private boolean fullFlag;
public MyQueue(int i){
this.jobArray = new IJob[i];
front = 0;
tail = 0;
size = 0;
}
//出队列
public synchronized IJob pop(){
this.notifyAll();
if(front == tail && !fullFlag){
try {
this.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
IJob job = jobArray[front];
jobArray[front] = null;
front = (front + 1)%jobArray.length;
size--;
fullFlag = false;
return job;
}
//入队列
public synchronized boolean put(IJob job) throws Exception{
this.notifyAll();
if(null == job){
throw new Exception("不能入null");
}
if(fullFlag){
try {
this.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
jobArray[tail] = job;
tail = (tail + 1)%jobArray.length;
size++;
if(front == tail) fullFlag = true;
return true;
}
public long getSize() {
return size;
}
public boolean isEmpty(){
return size==0?true:false;
}
public boolean isFull(){
return fullFlag;
}
public String toString(){
StringBuffer sb = new StringBuffer().append("[");
for(int i=0; i<jobArray.length-1; i++){
if(null != jobArray[i]){
sb.append(((JobImpl)jobArray[i]).name + ",");
}else{
sb.append("null" + ",");
}
}
if(jobArray[jobArray.length-1] != null){
sb.append(((JobImpl)jobArray[jobArray.length-1]).name + "]");
}else{
sb.append("null" + "]");
}
return sb.toString();
}
}
3、任务接口
用户任务必须实现这个接口
/*
* 任务接口
*/
public interface IJob {
public void doJob();
}
4、工作线程
即消费者。
理论上来说,工作线程可以是一个死循环,不停地检查任务队列中有无需要执行的任务。
/*
* 工作线程
*/
public class Worker extends Thread {
int _idx;
MyQueue q = new MyQueue(100);
public Worker(int i, MyQueue q){
this._idx = i;
this.q = q;
}
public void run(){
while(true){
IJob job = q.pop();
if(null == job){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
job.doJob();
}
}
public String toString(){
return "线程_" + _idx;
}
}
5 测试
/*
* 用户的任务
*/
class JobImpl implements IJob{
String name;
JobImpl(int i){
name = "["+i+"]";
}
public void doJob() {
System.out.println("[" + Thread.currentThread().getName() + "]执行" + name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
* 测试的main函数
*/
public static void main(String [] a) throws Exception{
IJob job = null;
MyQueue q = new MyQueue(10);
PoolManager pm = new PoolManager(2, q);
pm.execute();
for(int i=0; i<100; i++){
job = new JobImpl(i);
System.out.println(((JobImpl)job).name + "入队列");
q.put(job);
}
Thread.sleep(10*1000);
for(int i=101; i<150; i++){
job = new JobImpl(i);
System.out.println(((JobImpl)job).name + "入队列");
q.put(job);
}
}
二、优化后的线程池
这个线程池的例子有几个缺点:
1、向用户开放的接口太多,暴露了大部分内部结构;
2、队列、工作线程都采用的面向实现编程,拓展性不够好;
3、线程池只能启动无法手动停止。
解决方法:
1、只向用户开放3个接口:任务Interface、线程池的启动和停止、向池中添加任务;
2、队列、工作线程采用接口编程,后期可以扩展不同的队列及工作线程;
3、增加线程池的停止方法stop()。