1. 程式人生 > >java---線程池的使用

java---線程池的使用

能力 static 定時 功耗 title sched 無限制 我們 監控

1 線程池做什麽

網絡請求通常有兩種形式:

第一種,請求不是很頻繁,而且每次連接後會保持相當一段時間來讀數據或者寫數據,最後斷開,如文件下載,網絡流媒體等。

另一種形式是請求頻繁,但是連接上以後讀/寫很少量的數據就斷開連接。考慮到服務的並發問題,如果每個請求來到以後服務都為它啟動一個線程,那麽這對服務的資源可能會造成很大的浪費,特別是第二種情況。

因為通常情況下,創建線程是需要一定的耗時的,設這個時間為T1,而連接後讀/寫服務的時間為T2,當T1>>T2時,我們就應當考慮一種策略或者機制來控制,使得服務對於第二種請求方式也能在較低的功耗下完成。

通常,我們可以用線程池來解決這個問題,首先,在服務啟動的時候,我們可以啟動好幾個線程,並用一個容器(如線程池)來管理這些線程。

當請求到來時,可以從池中取一個線程出來,執行任務(通常是對請求的響應),當任務結束後,再將這個線程放入池中備用;

如果請求到來而池中沒有空閑的線程,該請求需要排隊等候。最後,當服務關閉時銷毀該池即可。

多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。
如果:T1 + T3 遠大於 T2,則可以采用線程池,以提高服務器性

線程池技術正是關註如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的

它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。

線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:

假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,

而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。

所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。

合理利用線程池能夠帶來三個好處:

第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。

第三:提高線程的可管理性

。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

但是要做到合理的利用線程池,必須對其原理了如指掌。

2 線程池的繼承架構

程序啟動一個新線程成本是比較高的,因為它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。

線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成為空閑狀態,等待下一個對象來使用。

在JDK5之前,我們必須手動實現自己的線程池,從JDK5開始,Java內置支持線程池

Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。

Executor是一個頂層接口,在它裏面只聲明了一個方法execute(Runnable),返回值為void,參數為Runnable類型,從字面意思可以理解,就是用來執行傳進去的任務的;

技術分享圖片

要配置一個線程池是比較復雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,

因此在Executors類裏面提供了一些靜態工廠,生成一些常用的線程池:

newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。

如果這個唯一的線程因為異常結束,那麽會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。

線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麽線程池會補充一個新線程。

newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麽就會回收部分空閑(60秒不執行任務)的線程,

當任務數增加時,此線程池又可以智能的添加新線程來處理任務。

此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。

newScheduledThreadPool:創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。

newSingleThreadExecutor:創建一個單線程的線程池。此線程池支持定時以及周期性執行任務的需求。

3 使用線程池步驟及案例

線程池的好處:線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成為空閑狀態,等待下一個對象來使用。

如何實現線程的代碼呢?

A:創建一個線程池對象,控制要創建幾個線程對象。

public static ExecutorService newFixedThreadPool(int nThreads)

B:這種線程池的線程可以執行:

可以執行Runnable對象或者Callable對象代表的線程

做一個類實現Runnable接口。

C:調用如下方法即可

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

D:我就要結束,可以嗎? 可以。

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

class MyRunnable implements Runnable{

public void run(){

for(int x=0;x<100;x++){

System.out.println(Thread.currentThread().getName()+":"+x);

}

}

}

public class ExecutorServiceDemo{

public static void main(String[]args){

// 創建一個線程池對象,控制要創建幾個線程對象。

ExecutorService pool=Executors.newFixedThreadPool(2);

// 可以執行Runnable對象或者Callable對象代表的線程

pool.submit(new MyRunnable());

pool.submit(new MyRunnable());

//結束線程池

pool.shutdown();

}

}

說明:

(1 newFixedThreadPool

是固定大小的線程池 有結果可見 我們指定2 在運行時就只有2個線程工作

若其中有一個線程異常 會有新的線程替代他

(2 shutdown方法有2個重載:

void shutdown() 啟動一次順序關閉,等待執行以前提交的任務完成,但不接受新任務。

List<Runnable> shutdownNow() 試圖立即停止所有正在執行的活動任務,暫停處理正在等待的任務,並返回等待執行的任務列表。

(3 submit 與 execute

3.1 submit是ExecutorService中的方法 用以提交一個任務

他的返回值是future對象 可以獲取執行結果

<T> Future<T> submit(Callable<T> task) 提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。

Future<?> submit(Runnable task) 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。

<T> Future<T> submit(Runnable task, T result) 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。

3.2 execute是Executor接口的方法

他雖然也可以像submit那樣讓一個任務執行 但並不能有返回值

void execute(Runnable command)

在未來某個時間執行給定的命令。該命令可能在新的線程、已入池的線程或者正調用的線程中執行,這由 Executor 實現決定。

(4 Future

Future 表示異步計算的結果。

它提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。

計算完成後只能使用 get 方法來獲取結果,如有必要,計算完成前可以阻塞此方法。

取消則由 cancel 方法來執行。還提供了其他方法,以確定任務是正常完成還是被取消了。一旦計算完成,就不能再取消計算。

如果為了可取消性而使用 Future 但又不提供可用的結果,則可以聲明 Future<?> 形式類型、並返回 null 作為底層任務的結果。

Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。

必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。 

也就是說Future提供了三種功能:

--判斷任務是否完成;

--能夠中斷任務;

--能夠獲取任務執行結果。

boolean cancel(boolean mayInterruptIfRunning) 試圖取消對此任務的執行。

V get() 如有必要,等待計算完成,然後獲取其結果。

V get(long timeout, TimeUnit unit) 如有必要,最多等待為使計算完成所給定的時間之後,獲取其結果(如果結果可用)。

boolean isCancelled() 如果在任務正常完成前將其取消,則返回 true。

boolean isDone() 如果任務已完成,則返回 true。

4 線程池簡單使用案例2

java.util.concurrent.Executors類的API提供大量創建連接池的靜態方法:

import java.concurrent.Executors;

import java.concurrent.ExecutorService;

public class JavaThreadPool{

public static void main(String[]args){

//創建一個可重用的固定線程數的線程池

ExecutorService pool=Executor.newFixedThreadPool(2);

//創建實現Runnable接口對象,Thread對象當然也實現了runnable接口

Thread t1=new MyThread();

Thread t2=new MyThread();

Thread t3=new MyThread();

Thread t4=new MyThread();

Thread t5=new MyThread();

//將線程放入到池中進行執行

pool.execute(t1);

pool.execute(t2);

pool.execute(t3);

pool.execute(t4);

pool.execute(t5);

//關閉線程池

pool.shutdown();

}

}

class MyThread extends Thread{

public void run(){

System.out.println(Thread.currentThread().getName()+"正在執行....");

}

}

5 單任務線程池:

//創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。

ExecutorService pool = Executors.newSingleThreadExecutor();

import java.util.concurrent.Executors;

import java.uti.concurrent.ExecutorService;

public class SingThreadPollDemo{

public static void main(Stringp[]args){

// 創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。

ExecutorService pool=Executors.newSingleThreadExecutor();

Runnable task1=new SingleTask();

Runnable task2=new SingleTask();

Runnable task3=new SingleTask();

pool.execute(task1);

pool.execute(task2);

pool.execute(task3);

pool.shutdown();

}

}

class SingleTask implements Runnable{

public void run(){

System.out.println(Thread.currentThread().getName() + "正在執行… …");

try{

Thread.sleep(3000);

}catch(InterruptedException e){

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + "執行完畢");

}

}

java---線程池的使用