在多線程開發中,當線程數量較多時,並且每個線程的執行時間較短,因而需要頻繁的創建線程和銷燬線程,這樣會大大較低系統的吞吐能力。這時就可以採用線程技術,實現線程執行完成後不會被銷燬,可以被反覆使用。假設一個服務器完成一項任務所需要的時間爲T1,創建一個線程的時間爲T2,銷燬一個線程的時間爲T3。當((T2+T3))/T1 的值較大時,才用線程池的技術就可以很好的提高服務器性能。
線程池顧名思義指的就是線程的池子,初始時,我們創建一定數量的線程放置在線程池,每當一個新的任務提交時,我們就可以從池子中取出一個空閒的線程去執行該任務,執行完後才能後,線程不會被銷燬,被重現放回線程池的空閒隊列中。
線程池技術正是關注如何縮短或調整線程的創建時間或銷燬線程時間的技術。我們正常情況下,會在啓動應用程序時,創建線程池,而退出應用時,銷燬線程池,因而在應用處理請求過程中只有執行服務的時間開銷。注意:需要根據系統的承受能力,調整線程池中工作線程的數目,防止因爲消耗過多的內存,而把服務器累趴下。
1、在Java類庫中提供了線程池的機制,常見的以下幾種線程池:
1) newScheduledThreadPool
創建一個定長線程池,支持定時及週期性任務執行,類似於Timer
package com.wygu.thread.study;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiThreadPool {
public static void main(String []argv){
TaskRunnable taskRunnableA = new TaskRunnable("ThreadA");
TaskRunnable taskRunnableB = new TaskRunnable("ThreadB");
ExecutorService executorService = Executors.newScheduledThreadPool(2);
executorService.execute(taskRunnableA);
executorService.execute(taskRunnableB);
}
}
class TaskRunnable implements Runnable{
private String threadNm;
public TaskRunnable(String threadNm) {
this.threadNm = threadNm;
}
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(this.threadNm+"---->"+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
程序運行結果爲:
ThreadB—->0
ThreadA—->0
ThreadA—->1
ThreadB—->1
ThreadA—->2
ThreadB—->2
ThreadA—->3
ThreadB—->3
ThreadA—->4
ThreadB—->4
2) newCachedThreadPool
創建一個可緩存的線程池,如果當前線程池的規模超出了處理需求,將回收空閒的線程;當需求增加時,會增加工作線程數量;線程池規模無限制;空閒線程被保留60秒。
3) newFixedThreadPool
創建一個固定長度的線程池,當到達線程最大數量時,線程池的規模將不再變化。
4) newSingleThreadExecutor
線程池中任意時刻只有一個線程,隊列中的任務按照順序執行。
2、工作中的應用
在後臺交易系統中,每天都會產生大量的流水日誌,如果系統實現實現日誌數據清理的機制功能,會導致日誌表中的數據日積月累,越來越多,嚴重影響到系統的性能。筆者剛工作時,接觸到的第一個系統就是這種情況,交易日誌表中存儲接近上千萬的數據,嚴重影響了交易的響應速度。爲解決這個問題,往往會創建兩張交易日誌表,實現按日輪換使用,利用定時任務清理即將使用的表中的記錄。
首先介紹ServletContextListener機制:
(1)接口ServletContextListener監聽 ServletContext對象的生命週期。
(2) 當Servlet容器啓動或終止Web應用時,就會觸發ServletContextEvent事件,該事件由ServletContextListener來處理。
(3) 在 ServletContextListener接口中定義了處理ServletContextEvent事件的兩個方法contextInitialized(),contextDestroyed()。
首先在web.xml中配置
<listener>
<listener-class> com.unionpay.cloudPayment.util.TimedTaskClearUtil</listener-class>
</listener>
下面創建類TimedTaskClearUtil,並繼承ServletContextListener,重寫方法contextInitialized(),contextDestroyed():
import java.sql.Connection;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class TimedTaskClearUtil implements ServletContextListener{
private static ScheduledThreadPoolExecutor service;
public void contextInitialized(ServletContextEvent sce) {
service = new ScheduledThreadPoolExecutor(2); //初始化線程池中線程數爲2
service.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);//當容器停止時,任務也將立刻停止
service.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
}
public void contextDestroyed(ServletContextEvent sce) {
try {
if(!service.isShutdown()){
service.shutdownNow();
System.out.println("*****************定時任務:日誌表清理銷燬成功******************");
}
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void execuTimedTask(){
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");// 設置日期格式
String curDateStr = sdf.format(new Date());
String nextTime = String.format("%s%s", curDateStr.substring(0, 8),"234000");
String cycleUnit = "H"; //H表示以小時爲單位,M表示以分鐘爲單位,S表示以秒爲單位
int cycleSpan = 24; //表示24 cycleUnit
long secondDiff = 0;
try {
Date curDate = sdf.parse(curDateStr);
Date nextDate= sdf.parse(nextTime);
secondDiff=(nextDate.getTime()-curDate.getTime())/1000; //距離第一次任務的執行還需要多長時間
} catch (ParseException e) {
e.printStackTrace();
}
Runnable runnable = new Runnable() {
public void run() {
/*執行日誌表的清理
**清理時建議使用truncate操作,減少磁盤碎片
*/
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.scheduleAtFixedRate(runnable, secondDiff, revToSecond(cycleUnit,cycleSpan), TimeUnit.SECONDS);//定時任務按照固定的頻率執行
}
private static long revToSecond(String cycleUnit,int cycleSpan){
long timeSpan = 0;
if("H".equals(cycleUnit)){
timeSpan = cycleSpan*3600;
}else if("M".equals(cycleUnit)){
timeSpan = cycleSpan*60;
}else if("S".equals(cycleUnit)){
timeSpan = cycleSpan;
}
return timeSpan;
}
}
在啓動servlet中,可以直接加載定時任務,比如在InitServlet中,調用:
TimedTaskClearUtil.execuTimedTask();
線程池除了可以直接使用Java Api提供的類創建外,也可以自己創建。
2、自定義線程池
一個線程池包括以下四個基本組成部分:
(1)線程池管理器(ThreadPool):用於創建並管理線程池,包括創建線程池,銷燬線程池,添加新任務;
(2)工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環式執行任務;
(3)任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;
(4)任務隊列(taskQueue):用於存放沒有處理的任務,提供一種緩衝機制。
自定義線程池代碼:參考博客:http://www.cnblogs.com/kinghitomi/archive/2012/01/19/2327418.html
創建線程管理器
package com.wygu.thread.study;
import java.util.concurrent.LinkedBlockingQueue;
/**
*
* @author guweiyu
*@category 一個完整的線程至少需要包含以下四個部分:線程池管理器(ThreadPoll),工作線程(WorkThread),任務接口(Task),任務隊列(TaskQueue)
*/
//線程池管理器
public final class ThreadPoolExecutOwn {
// 設置線程池中默認線程數爲5
private static int worker_thread_num = 5;
private static int default_thread_num = 5;
// 工作線程
private WorkThread[] workThread;
// 未處理的任務
public static volatile int finished_task = 0;
// 任務隊列,作爲一個緩衝
public static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
private static ThreadPoolExecutOwn threadPool;
// 創建具有默認線程個數的線程池
private ThreadPoolExecutOwn() {
this(default_thread_num);
}
// 創建線程池中的線程,worker_thread_num爲線程池中工作線程的個數
private ThreadPoolExecutOwn(int worker_thread_num) {
ThreadPoolExecutOwn.worker_thread_num = worker_thread_num;
workThread = new WorkThread[worker_thread_num];
for (int i = 0; i < worker_thread_num; i++) {
workThread[i] = new WorkThread();
workThread[i].start();
}
}
// 獲得默認值default_thread_num的線程池
public static ThreadPoolExecutOwn getThreadPool() {
return getThreadPool(ThreadPoolExecutOwn.default_thread_num);
}
// 獲得一個指定線程個數的線程池,worker_num(>0)爲線程池中工作線程的個數
public static ThreadPoolExecutOwn getThreadPool(int worker_num) {
if (worker_num <= 0)
worker_num = ThreadPoolExecutOwn.default_thread_num;
if (threadPool == null)
threadPool = new ThreadPoolExecutOwn(worker_num);
return threadPool;
}
// 執行任務,將任務加入到任務隊列中,等待線程池管理器調用
public void execute(Runnable task) {
synchronized (ThreadPoolExecutOwn.taskQueue) {
ThreadPoolExecutOwn.taskQueue.add(task);
ThreadPoolExecutOwn.taskQueue.notify();
}
}
// 銷燬線程池,方法保證在所有任務都完成的情況下才會銷燬所有線程,否則等待任務完成才銷燬
public void destroy() {
while (!ThreadPoolExecutOwn.taskQueue.isEmpty()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 工作線程停止工作,且置爲null
for (int i = 0; i < worker_thread_num; i++) {
workThread[i].stopWorker();
workThread[i] = null;
}
threadPool=null;
ThreadPoolExecutOwn.taskQueue.clear();// 清空任務隊列
}
// 返回工作線程的個數
public int getWorkThreadNumber() {
return worker_thread_num;
}
// 返回已完成任務的個數,這裏的已完成是隻出了任務隊列的任務個數,可能該任務並沒有實際執行完成
public int getFinishedTasknumber() {
return finished_task;
}
// 返回任務隊列的長度,即還沒處理的任務個數
public int getWaitTasknumber() {
return ThreadPoolExecutOwn.taskQueue.size();
}
}
創建工作線程
package com.wygu.thread.study;
/*
* @author guweiyu
*/
public class WorkThread extends Thread {
private boolean isRunning = true; //用於判斷工作線程是否有效,能否繼續執行
//如果任務隊列爲空,則工作線程等待,否則從隊列中取出任務繼續執行
@Override
public void run() {
Runnable r = null;
while (isRunning) {
synchronized (ThreadPoolExecutOwn.taskQueue) {
while (isRunning && ThreadPoolExecutOwn.taskQueue.isEmpty()) {// 隊列爲空
try {
ThreadPoolExecutOwn.taskQueue.wait(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!ThreadPoolExecutOwn.taskQueue.isEmpty())
r = ThreadPoolExecutOwn.taskQueue.remove();// 取出任務
}
if (r != null) {
r.run();// 執行任務
}
ThreadPoolExecutOwn.finished_task++;
r = null;
}
}
// 停止工作,讓該線程自然執行完run方法,自然結束
public void stopWorker() {
isRunning = false;
}
}
創建工作任務
package com.wygu.thread.study;
public class WorkTask implements Runnable{
private String taskName = null;
public WorkTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("任務開始執行:"+this.taskName);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("任務完成:"+this.taskName);
}
}
Main方法調用
package com.wygu.thread.study;
public class Main {
public static void main(String[] args) {
ThreadPoolExecutOwn threadPoolExecutOwn = ThreadPoolExecutOwn.getThreadPool(3);//創建線程數爲4的線程池
WorkTask []workTask = new WorkTask[100];
for(int i=0;i<10;i++){
workTask[i] = new WorkTask("TASK"+i);
threadPoolExecutOwn.execute(workTask[i]);
}
}
}
程序運行結果爲
TASK0:開始執行*
TASK2:開始執行*
TASK1:開始執行*
TASK0:執行結束###
TASK2:執行結束###
TASK1:執行結束###
TASK4:開始執行*
TASK3:開始執行*
TASK5:開始執行*
TASK4:執行結束###
TASK5:執行結束###
TASK3:執行結束###
TASK6:開始執行*
TASK8:開始執行*
TASK7:開始執行*
TASK6:執行結束###
TASK8:執行結束###
TASK9:開始執行*
TASK7:執行結束###
TASK9:執行結束###