1. 程式人生 > >由Java執行緒池的例子到Tomcat執行緒池

由Java執行緒池的例子到Tomcat執行緒池

執行緒池的作用:
 
執行緒池作用就是限制系統中執行執行緒的數量。
 
根據系統的環境情況,可以自動或手動設定執行緒數量,達到執行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用執行緒池控制執行緒數量,其他執行緒排隊等候。一個任務執行完畢,再從佇列的中取最前面的任務開始執行。若佇列中沒有等待程序,執行緒池的這一資源處於等待。當一個新任務需要執行時,如果執行緒池中有等待的工作執行緒,就可以開始運行了;否則進入等待佇列。

為什麼要用執行緒池:
 

  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請求的始終是由一個執行緒全程負責,沒有切換到其他執行緒處理,少了這種執行緒間的切換是否更有效率呢?我還不能確認。不過這種使用方式確實有別於傳統模式,有種耳目一新的感覺。

相關推薦

Java執行例子Tomcat執行

執行緒池的作用:   執行緒池作用就是限制系統中執行執行緒的數量。   根據系統的環境情況,可以自動或手動設定執行緒數量,達到執行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用執行緒池控制執行緒數量,其他執行緒排隊等候。一個任務執行完畢,再從佇列的中取最前面的

java -- 執行例子ExecutorService

package com.threadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.con

Java執行例子

/**  *   */ package iotest.serversocket;   im

Java併發(11)- 有關執行的10個問題

引言 在日常開發中,執行緒池是使用非常頻繁的一種技術,無論是服務端多執行緒接收使用者請求,還是客戶端多執行緒處理資料,都會用到執行緒池技術,那麼全面的瞭解執行緒池的使用、背後的實現原理以及合理的優化執行緒池的大小等都是非常有必要的。這篇文章會通過對一系列的問題的解答來講解執行緒池的基本功能以及背後的原理,

Java併發程式設計:4種執行和緩衝佇列BlockingQueue

一. 執行緒池簡介 1. 執行緒池的概念:           執行緒池就是首先建立一些執行緒,它們的集合稱為執行緒池。使用執行緒池可以很好地提高效能,執行緒池在系統啟動時即建立大量空閒的執行緒,程式將一個任務傳給執行緒池,執行緒池就會啟動一

spring mvc tomcat 執行的坑

1 配置tomcat  執行緒池設定為20個執行緒處理請求 2 後臺框架是springmvc   3 模擬10個請求 4  發現tomcat執行緒池沒一個幹活的 5 幹活的是spring自己建立的執行緒 為什麼springmvc

使用Java佇列來處理日誌資訊(執行的使用)

阿里的規範是使用new ThreadPoolExecutor()來建立執行緒池,二不是使用Excutor的靜態工具類來建立執行緒池,具體可以檢視部落格(兩篇):   https://blog.csdn.net/angus_Lucky/article/details/798

【小家javaJava執行之---ForkJoinPool執行的使用以及原理

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

java執行(13)執行

當程式中需要大量並且生存週期很短的執行緒時候,可以考慮使用執行緒池,執行緒池的工作原理是線上程池創立的時候就建立大量空閒的執行緒,當一個Runnable或者Callable物件傳到執行緒池的時候,執行緒池就啟動一執行緒來執行他們的run或者call方法,當run或者call方

Java執行系列--“JUC執行”01之 執行架構

概要 前面分別介紹了”Java多執行緒基礎”、”JUC原子類”和”JUC鎖”。本章介紹JUC的最後一部分的內容——執行緒池。內容包括: 執行緒池架構圖 執行緒池示例 執行緒池架構圖 執行緒池的架構圖如下: 1、Executor

Java執行系列--“JUC執行”05之 執行原理(四)

概要 本章介紹執行緒池的拒絕策略。內容包括: 拒絕策略介紹 拒絕策略對比和示例 拒絕策略介紹 執行緒池的拒絕策略,是指當任務新增到執行緒池中被拒絕,而採取的處理措施。 當任務新增到執行緒池中之所以被拒絕,可能是由於:第一,執行緒池異常關閉。第二,任務數量

執行實現原理(Executor框架),java提供常用的幾種執行、死鎖產生條件和避免

 為什麼使用執行緒池 伺服器應用程式中經常出現的情況是:單個任務處理的時間很短而請求的數目卻是巨大的。如果每個請求對應一個執行緒(thread-per-request)方法的不足之一是:為每個請求建立一個新執行緒的開銷很大;為每個請求建立新執行緒的伺服器在建立和銷燬執行緒上

java中常見的四種執行的區別

在使用執行緒時,可以這樣建立一個執行緒池: ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(()-> System.out.println("run ..

java 執行 (二) 執行

  package cn.sasa.demo2; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public stat

Java併發任務處理之Executor執行

乾貨 import org.junit.After; import org.junit.Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public c

java執行之callable+Executor執行例項

package main.java; import java.sql.Time; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.con

java佇列、棧和多執行結合使用的例子

剛來公司幾天,無意中聽到其他的開發組有用到佇列這個知識點,我沒用過也沒了解過,今天花時間補補這塊知識,整理了網上的一些資料。 佇列其實 所指生活中排隊的現象,去商場購物,付款時需要排隊, 買飯時需要排隊, 好多事情都是需要排隊, 排在第一位的則先處理,結束後, 後面的人都

Java高併發程式設計》學習 --3.2 執行複用:執行

1)什麼是執行緒池 為了避免系統頻繁地建立和銷燬執行緒,我們可以讓建立的執行緒進行復用。執行緒池中,總有那麼幾個活躍執行緒。當你需要使用執行緒時,可以從池子中隨便拿一個空閒執行緒,當完成工作時,並不急著關閉執行緒,而是將整個執行緒退回到池子,方便其他人使用。 2)JDK對執

Java實現終止執行中正在執行的定時任務

貼個廣告 樓主的部落格已全部搬遷至自己的部落格,感興趣的小夥伴請移步haifeiWu與他朋友們的部落格專欄 源於開發 最近專案中遇到了一個新的需求,就是實現一個可以動態新增定時任務的功能。說到這裡,有人可能會說簡單啊,使用quartz就好了,簡單粗暴。然而

Java併發程式設計札記-(六)JUC執行-01概述

前面的例子中總是需要執行緒時就建立,不需要就銷燬它。但頻繁建立和銷燬執行緒是很耗資源的,在併發量較高的情況下頻繁建立和銷燬執行緒會降低系統的效率。執行緒池可以通過重複利用已建立的執行緒降低執行緒建立和銷