標籤:java 線程池
基本認識
以下摘自百度百科
線程池的概念:線程池是一種多執行緒形式,處理過程中將任務添加到隊列,然後在建立線程後自動啟動這些任務。
線程池的作用:在物件導向編程中,建立和銷毀對象是很費時間的,因為建立一個對象要擷取記憶體資源或者其它更多資源。在Java中更是如此,虛擬機器將試圖跟蹤每一個對象,以便能夠在對象銷毀後進行記憶體回收。所以提高服務程式效率的一個手段就是儘可能減少建立和銷毀對象的次數,特別是一些很耗資源的對象建立和銷毀。如何利用已有對象來服務就是一個需要解決的關鍵問題,其實這就是一些”池化資源”技術產生的原因。比如大家所熟悉的資料庫連接池正是遵循這一思想而產生的。
總而言之:線程池就是系統通過池化資源的概念,達到節省資源的一種手段。
關於線程與任務:《Java變成思想第四版》裡面描述得很清楚,一言概之:線程是用來驅動任務的。初學時以為一個線程就是一個任務,其實不然。
Java線程池
Java有四種常見的線程池,在java.util.concurrernt.Executors類裡面以靜態工廠形式返回:
- newCachedThreadPool:建立一個可根據需要建立新線程的線程池,但是在以前構造的線程可用時將重用它們。(Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available.)
- newFixedThreadPool: 建立一個可重用固定線程數的線程池,以共用的無界隊列方式來運行這些線程。(Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.)
- newScheduledThreadPool:建立一個線程池,它可安排在給定延遲後運行命令或者定期地執行。( Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.)
- newSingleThreadExecutor: 建立一個使用單個線程的線程池,以無界隊列方式來運行該線程。(Creates an Executor that uses a single worker thread operating off an unbounded queue.)
觀察這幾個靜態工廠,對於newCachedThreadPool、newSingleThreadExecutor,API用戶端直接調用即可;而對於newFixedThreadPool,用戶端需要關注參數:int nThreads;newScheduledThreadPool,用戶端需要關注參數:int corePoolSize。
於是我們來看看源碼裡面這些參數都代表什麼意思
以建立CachedThreadPool為例:
ExecutorService ctp = Executors.newCachedThreadPool();
newCachedThreadPool()方法的源碼如下:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
我們直接來到ThreadPoolExecutor類,看下幾個構造器的參數都代表什麼意思。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... }
由於暫時只為入門的基礎認識,所以後面幾個參數我們不關心,姑且認為他們是線程池底層操作相關的參數,我們只看前面四個甚至只看前面兩個API用戶端會使用的參數。
源碼檔案的:
- corePoolSize:常駐線程池的線程數
- maximunPoolSize:線程池中允許的最多線程數
- keepAliveTime:當現有線程數多於常駐的數目時,空閑線程若在這個最大時間長度還沒等待到新任務,則被終止。
- unit:keepAliveTimed的時間單位
再來看一下建立幾種線程池時前面幾個參數預設的都是啥:
1、Cached線程池:可以看到常駐的數目為0,最大的線程數目可以達到很大,當線程空閑時60秒內沒有新任務則終止。從名字就可以看出這個效果了。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
2、Fixed線程池:可以看到常駐的數目由用戶端決定,最大的線程數目同樣也是,當線程空閑時則馬上被終止。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
3、Single線程池:只有也只能有一條背景工作執行緒,沒有任務就終止。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
4、Scheduled線程池:常駐線程數由用戶端決定
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
簡單一實例
public class Test { private static ExecutorService cached = Executors.newCachedThreadPool(); private static ExecutorService fixed = Executors.newFixedThreadPool(3); private static FileWriter fw; static { try { fw = new FileWriter("j:/log.txt"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args) { // TODO Auto-generated method stub Runnable sellingApple = new Selling("apple"); Runnable sellingBanana = new Selling("banana"); for (int i = 0; i < 5; i++) { cached.execute(sellingApple); } cached.execute(sellingBanana); } private static class Selling implements Runnable { private int count = 500; private String item; public Selling(String item) { this.item = item; } public void run() { // TODO Auto-generated method stub while (true) { synchronized (this) { if (count < 1) { break; } sell(); } try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } cached.shutdown(); } private void sell() { try { fw.write(Thread.currentThread().getName() + " sold " + item + ": #" + count-- + "\n"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }}
結果:我們總共在緩衝線程池裡面開啟了6條線程,前面5條是用來驅動賣蘋果這個任務,最後的第6條用來賣香蕉。
假設把上面主程式的cached線程池換成參數為3的fixed線程池,結果如下:為什麼必須是3條線程賣完蘋果後才能賣香蕉呢?我們開了5條線程去賣蘋果,和1條線程去賣香蕉,但是fixed線程池最多隻能有3條線程,所以賣香蕉這個任務在隊列裡面等待著。等這3條線程賣完全部蘋果後,就會複用其中1條線程去賣香蕉了。如果我們把參數改為6,則和上述結果一樣。
小結:不同線程池作用不同,execute開啟一條線程去驅動一個任務,shutdown()關閉線程池回收資源,但任務還是會按隊列順序執行完(Initiates an orderly shutdown in which previously submitted tasks are executed),當前線程數不夠驅動任務時,任務就在隊列等著。
Java:線程池基礎