由Java线程池的例子到Tomcat线程池

来源:互联网 发布:js鼠标移入事件 编辑:程序博客网 时间:2024/04/30 08:38

线程池的作用:
 
线程池作用就是限制系统中执行线程的数量。
 
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

 

为什么要用线程池:
 

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
  2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

 
本文以一个线程池的小例子,由浅入深到Tomcat线程池。例子如下,通过此例子,也能很好的学习和复习Java线程的API。
 
线程池类
 

package tast;import java.util.LinkedList;public class ThreadPool extends ThreadGroup { private boolean isClosed = false;  //线程池是否关闭 private LinkedList workQueue;      //工作队列 private static int threadPoolID = 1;  //线程池的id public ThreadPool(int poolSize) {  //poolSize 表示线程池中的工作线程的数量  super(threadPoolID + "");      //指定ThreadGroup的名称  setDaemon(true);               //继承到的方法,设置是否守护线程池  workQueue = new LinkedList();  //创建工作队列  for(int i = 0; i < poolSize; i++) {   new WorkThread(i).start();   //创建并启动工作线程,线程池数量是多少就创建多少个工作线程  } } /** 向工作队列中加入一个新任务,由工作线程去执行该任务*/ public synchronized void execute(Runnable task) {  if(isClosed) {   throw new IllegalStateException();  }  if(task != null) {   workQueue.add(task);//向队列中加入一个任务   notify();    //唤醒一个正在getTask()方法中待任务的工作线程  } } /** 从工作队列中取出一个任务,工作线程会调用此方法*/ private synchronized Runnable getTask(int threadid) throws InterruptedException {  while(workQueue.size() == 0) {   if(isClosed) return null;   System.out.println("工作线程"+threadid+"等待任务...");   wait();    //如果工作队列中没有任务,就等待任务  }  System.out.println("工作线程"+threadid+"开始执行任务...");  return (Runnable) workQueue.removeFirst(); //反回队列中第一个元素,并从队列中删除 } /** 关闭线程池 */ public synchronized void closePool() {  if(! isClosed) {   waitFinish();        //等待工作线程执行完毕   isClosed = true;   workQueue.clear();  //清空工作队列   interrupt();   //中断线程池中的所有的工作线程,此方法继承自ThreadGroup类  } } /** 等待工作线程把所有任务执行完毕*/ public void waitFinish() {  synchronized (this) {   isClosed = true;   notifyAll();   //唤醒所有还在getTask()方法中等待任务的工作线程  }  Thread[] threads = new Thread[activeCount()]; //activeCount() 返回该线程组中活动线程的估计值。  int count = enumerate(threads); //enumerate()方法继承自ThreadGroup类,根据活动线程的估计值获得线程组中当前所有活动的工作线程  for(int i =0; i < count; i++) { //等待所有工作线程结束   try {    threads[i].join(); //等待工作线程结束   }catch(InterruptedException ex) {    ex.printStackTrace();   }  } } /**  * 内部类,工作线程,负责从工作队列中取出任务,并执行  * @author sunnylocus  */ private class WorkThread extends Thread {  private int id;  public WorkThread(int id) {   //父类构造方法,将线程加入到当前ThreadPool线程组中   super(ThreadPool.this,id+"");   this.id =id;  }  public void run() {   while(! isInterrupted()) {  //isInterrupted()方法继承自Thread类,判断线程是否被中断    Runnable task = null;    try {     task = getTask(id);  //取出任务    }catch(InterruptedException ex) {     ex.printStackTrace();    }    //如果getTask()返回null或者线程执行getTask()时被中断,则结束此线程    if(task == null) return;    try {     task.run();  //运行任务    }catch(Throwable t) {     t.printStackTrace();    }   }//  end while  }//  end run }// end workThread}

 
2.测试类
 

package tast;&nbsp;public class ThreadPoolTest { public static void main(String[] args) throws InterruptedException {  ThreadPool threadPool = new ThreadPool(3); //创建一个有个3工作线程的线程池  Thread.sleep(500); //休眠500毫秒,以便让线程池中的工作线程全部运行  //运行任务  for (int i = 0; i <=5 ; i++) { //创建6个任务   threadPool.execute(createTask(i));  }  threadPool.waitFinish(); //等待所有任务执行完毕  threadPool.closePool(); //关闭线程池 } private static Runnable createTask(final int taskID) {  return new Runnable() {   public void run() {   // System.out.println("Task" + taskID + "开始");    System.out.println("Hello world");   // System.out.println("Task" + taskID + "结束");   }  }; }}

 
结果返回:
 
工作线程0等待任务…
工作线程1等待任务…
工作线程2等待任务…
工作线程0开始执行任务…
Hello world
工作线程0开始执行任务…
Hello world
工作线程0开始执行任务…
Hello world
工作线程0开始执行任务…
Hello world
工作线程0开始执行任务…
Hello world
工作线程2开始执行任务…
Hello world

 

Tomcat是使用最广的Java Web容器,功能强大,可扩展性强。最新版本的Tomcat(5.5.17)为了提高响应速度和效率,使用了Apache Portable Runtime(APR)作为最底层,使用了APR中包含Socket、缓冲池等多种技术,性能也提高了。APR也是Apache HTTPD的最底层。可想而知,同属于ASF(Apache Software Foundation)中的成员,互补互用的情况还是很多的,虽然使用了不同的开发语言。
 

Tomcat 的线程池位于tomcat-util.jar文件中,包含了两种线程池方案。方案一:使用APR的Pool技术,使用了JNI;方案二:使用Java实现的ThreadPool。这里介绍的是第二种。如果想了解APR的Pool技术,可以查看APR的源代码。
 
ThreadPool默认创建了5个线程,保存在一个200维的线程数组中,创建时就启动了这些线程,当然在没有请求时,它们都处理“等待”状态(其实就是一个while循环,不停的等待notify)。如果有请求时,空闲线程会被唤醒执行用户的请求。
 
具体的请求过程是: 服务启动时,创建一个一维线程数组(maxThread=200个),并创建空闲线程(minSpareThreads=5个)随时等待用户请求。 当有用户请求时,调用 threadpool.runIt(ThreadPoolRunnable)方法,将一个需要执行的实例传给ThreadPool中。其中用户需要执行的实例必须实现ThreadPoolRunnable接口。 ThreadPool 首先查找空闲的线程,如果有则用它运行要执行ThreadPoolRunnable;如果没有空闲线程并且没有超过maxThreads,就一次性创建 minSpareThreads个空闲线程;如果已经超过了maxThreads了,就等待空闲线程了。总之,要找到空闲的线程,以便用它执行实例。找到后,将该线程从线程数组中移走。 接着唤醒已经找到的空闲线程,用它运行执行实例(ThreadPoolRunnable)。 运行完ThreadPoolRunnable后,就将该线程重新放到线程数组中,作为空闲线程供后续使用。
 
由此可以看出,Tomcat的线程池实现是比较简单的,ThreadPool.java也只有840行代码。用一个一维数组保存空闲的线程,每次以一个较小步伐(5个)创建空闲线程并放到线程池中。使用时从数组中移走空闲的线程,用完后,再“归还”给线程池
 
ThreadPool提供的仅仅是线程池的实现,而如何使用线程池也是有很大学问的。让我们看看Tomcat是如何使用ThreadPool的吧。
 
Tomcat有两种EndPoint,分别是AprEndpoint和PoolTcpEndpoint。前者自己实现了一套线程池(其实这和Tomcat 老版本的方案是相同的,至今Tomcat中还保留着老版本的线程池,PoolTcpEndpoint也有类似的代码,通过“策略”可以选择不同的线程池方案)。我们只关注PoolTcpEndpoint如何使用ThreadPool的。
 
首先,PoolTcpEndpoint创建了一个ThreadPoolRunnable实例——LeaderFollowerWorkerThread,实际上该实例就是接收(Accept)并处理(Process)用户socket请求。接着将该实例放进ThreadPool中并运行,此时就可以接收用户的请求了。
 
当有Socket请求时,LeaderFollowerWorkerThread首先获得了Socket实例,注意此时LeaderFollowerWorkerThread并没有急着处理该Socket,而是在响应Socket消息前,再次将LeaderFollowerWorkerThread放进ThreadPool中,从而它(当然是另外一个线程了)可以继续处理其他用户的Socket请求;接着,拥有Socket的LeaderFollowerWorkerThread再来处理该用户的Socket请求。
 
整个过程与传统的处理用户Socket请求是不同的,也和Tomcat老版本不同。传统的处理方法是:有一个后台运行的监听线程负责统一处理接收(注意只是“接收”)Socket请求,当有新的Socket请求时,将它赋值给一个Worker线程(通常是唤醒该线程),并有后者处理Socket请求,监听线程继续等待其他Socket请求。所以整个过程中有一个从Listener到Worker切换的过程。
 
而新版本Tomcat很有创造性的使用了另外一种方法,正如前文所描述的,接收和处理某个用户Socket请求的始终是由一个线程全程负责,没有切换到其他线程处理,少了这种线程间的切换是否更有效率呢?我还不能确认。不过这种使用方式确实有别于传统模式,有种耳目一新的感觉。