这段代码来源于孙卫琴老师的《java网络编程精解》里,这里通过分析一下这本书里线程池的实现方式来了解线程池的工作原理。首先上代码~~ 这里为了测试方便就直接把测试代码写在了线程池的实现类中间了,就是那俩个static函数。
package compass.my.thread;
import java.util.LinkedList;
public class MyThreadPool
extends ThreadGroup
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
int size = 6 ;
int runTasks = 9 ;
MyThreadPool pool = new MyThreadPool(size);
for(int i = 0 ; i < runTasks ; i++)
{
pool.execute(createTask(i));
}
pool.join();
pool.close();
}
public static Runnable createTask( final int taskID)
{
return new Runnable()
{
public void run()
{
System.out.println("Task :" + taskID + " start");
try
{
Thread.sleep(1000);
}catch(InterruptedException e)
{
System.out.println(e);
}
System.out.println("Task :" + taskID + " end");
}
};
}
private boolean isClosed = false ;
private LinkedList<Runnable> taskQueue ;
private static int threadPoolID = 0 ;
private int threadID ;
/**
*
*
* 内部类,工作线程,负责从工作队列中取出任务并执行
* @author BaiyangTX
*
*/
class WorkThread
extends Thread
{
public WorkThread()
{
super(MyThreadPool.this ,"WorkThread-"+(threadID++));
}
@Override
public void run()
{
while(true)
{
Runnable task = null ;
try
{
task = getTask();//取出任务
}catch(InterruptedException e)
{
e.printStackTrace();
}
if(task == null )
{// 当取出任务为空时,结束当前工作线程
return ;
}
try
{// 执行取得的任务
task.run();
}catch(Throwable t)
{
t.printStackTrace();
}
}
}
}
/**
* 构造方法,构造一个线程池
* @param poolSize-线程池的大小,即工作线程的数目
*/
public MyThreadPool(int poolSize)
{
super("ThreadPool-"+(threadPoolID++));
setDaemon(true);
taskQueue = new LinkedList<Runnable>();
for(int i = 0 ; i < poolSize ; i++)
{
new WorkThread().start();
}
}
/**
* 想工作队列中提交一个任务,并且唤醒一个等待任务的工作线程去执行改任务
* @param task-需要被提交的任务
*/
public synchronized void execute(Runnable task)
{
if(isClosed)
{
throw new IllegalStateException();
}
if(task != null )
{
taskQueue.add(task);
notify();
}
}
/**
* 从工作队列中获取一个任务,如果没有任务可获取,则挂起调用该方法的线程,直到任务队列中有任务。
* @return-工作队列中的任务,如果线程池已经关闭,则返回null值
* @throws InterruptedException
*/
protected synchronized Runnable getTask() throws InterruptedException
{
while(taskQueue.size() == 0)
{
if(isClosed)
return null ;
wait();
}
return taskQueue.removeFirst();
}
/**
* 关闭线程池,清空任务队列,并且中断所有正在执行的工作线程
*/
public synchronized void close()
{
if(!isClosed)
{
isClosed = true ;
taskQueue.clear();
interrupt();
}
}
public void join()
{
synchronized(this)
{
isClosed = true ;
notifyAll();
}
Thread[] threads = new Thread[activeCount()] ;
int count = enumerate(threads);
for(int i = 0 ; i < count ; i++)
{
try
{
threads[i].join();
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
可以看到,线程池类除了构造函数外对外有3个函数可供调用。它们分别是
execute(Runnable task) :该函数用于向线程池池中提交任务。
join() : 调用该方法将等待线程池中所有的任务执行完毕。
close() : 关闭线程池,未执行的任务讲不会被执行,当前正在执行的任务会执行完毕。
同时还提供了一个protected方法。该方法由线程池的内部工作类调用。之所以被定义为protected类型是为了可以在该类被继承时,可以重写该方法。
该线程池类还定义了一个内部工作类,实际上正是该工作类的对象在执行着用户提交的任务。
public MyThreadPool(int poolSize)
可以看到构造方法有一个参数,size,这个参数决定了线程池对象将会持有有多少个工作线程,以后用户提交的所有的任务(task)都是由这些工作线程去执行,所以合理的安排工作线程的数目是必须的。当然这也是线程池的用处之一。
private LinkedList<Runnable> taskQueue ;
线程池持有一个任务列表。用户通过execute方法提交计算任务,任务会被添加到任务列表中,等待着被工作线程获取并执行。
private boolean isClosed = false ;
可以注意到,线程池还持有一个boolean变量isClosed。当线程池被关闭时,这个变量会被设置会true。
工作线程的run方法是一个死循环,所有的工作线程都在线程池对象被实例化的时候创建并执行。工作线程通过线程池对象提供的protected方法getTask从任务队列中获取任务。如果任务队列中有任务,则取出任务,否则线程将被挂起,直到有任务被提交。如果线程池已经关闭,则返回null值。
获取任务后判断,不为空的话就执行任务。
protected synchronized Runnable getTask() throws InterruptedException
{
while(taskQueue.size() == 0)
{
if(isClosed)
return null ;
wait();
}
return taskQueue.removeFirst();
}
分析getTask方法,首先判断任务队列是否为空,不为空的话返回一个任务,否则判断,当线程池已经关闭则返回一个null,否则调用wait方法,挂起调用这个方法的工作线程。注意到这个方法是同步的,所以当一个工作线程被挂起的时候,由于其他的线程无法获得当前对象的锁,所以必须等待被挂起的线程获得任务或得到null才可以调用这个方法。
这里的wait方法是线程池对象的方法,所以被挂起的工作线程讲被加入当前线程对象的等待队列中,这时候我们分析execute方法。
public synchronized void execute(Runnable task)
{
if(isClosed)
{
throw new IllegalStateException();
}
if(task != null )
{
taskQueue.add(task);
notify();
}
}
当然用户调用了execute方法后,任务除了被放入任务队列中,还会调用当前线程对象的notify方法,这个方法将会使被加入当前线程池对象的wait队列中的第一个工作线程进入就绪状态并且尽快执行。
是不是和生产者消费者的例子很相似??
任务(task)就是产品,工作线程就是消费者,用户是生产者,工作线程获取产品,没有产品就等待,用户生产产品,生产产品时将通知消费者。
这就是线程池中工作调度的形式。
最后看一下join方法,首先设置isClosed为true。
synchronized(this)
{
isClosed = true ;
notifyAll();
}
这个操作位于同步区域内,然后获取所有的活动工作线程,依次调用join方法等待执行完毕。
当用户调用join方法后,等待所有的任务被执行完毕后,将释放所有的线程资源。关闭线程池。
public synchronized void close()
{
if(!isClosed)
{
isClosed = true ;
taskQueue.clear();
interrupt();
}
}
close方法与join类似,不过它不等待所有任务执行完毕,直接把任务队列clear掉,然后打断所有的正在工作的工作线程,注意,这里调用的interrupt()方法是继承自ThreadGroup的方法,所有处于阻塞状态的线程会得到一个InterruptedException,但是活动中的线程不会得到异常,但是调用isInterrupt方法会返回true值。