在前面的全部示例中,由新的線程(由其Runnable
對象定義)和線程自己(由Thread
對象定義)完成的任務之間存在緊密的聯繫,這適用於小型應用程序,但在大型應用程序中,將線程管理和建立與應用程序的其他部分分開是有意義的,封裝這些函數的對象稱爲執行器,如下小節詳細描述了執行器。html
java.util.concurrent
包定義了三個執行器接口:java
Executor
,一個支持啓動新任務的簡單接口。ExecutorService
,Executor
的子接口,它添加了有助於管理生命週期的功能,包括單個任務和執行器自己。ScheduledExecutorService
,ExecutorService
的子接口,支持未來和/或按期執行任務。一般,引用執行器對象的變量被聲明爲這三種接口類型之一,而不是執行器類類型。git
Executor接口提供單個方法execute
,旨在成爲常見線程建立語法的替代方法,若是r
是Runnable
對象,而且e
是Executor
對象,則能夠替換github
(new Thread(r)).start();
爲算法
e.execute(r);
可是,execute
的定義不太具體,低級別語法建立一個新線程並當即啓動它,根據Executor
實現,execute
可能會作一樣的事情,但更有可能使用現有的工做線程來運行r
,或者將r
放在隊列中以等待工做線程變爲可用(咱們將在線程池的部分中描述工做線程)。segmentfault
java.util.concurrent
中的執行器實現旨在充分利用更高級的ExecutorService
和ScheduledExecutorService
接口,儘管它們也能夠與基本Executor
接口一塊兒使用。api
ExecutorService接口使用相似但更通用的submit
方法補充execute
,與execute
同樣,submit
接受Runnable
對象,但也接受Callable對象,這容許任務返回一個值。submit
方法返回一個Future對象,該對象用於檢索Callable
返回值並管理Callable
和Runnable
任務的狀態。數組
ExecutorService
還提供了提交大量Callable
對象的方法,最後,ExecutorService
提供了許多用於管理執行器關閉的方法,爲了支持當即關閉,任務應該正確處理中斷。服務器
ScheduledExecutorService接口使用schedule
補充其父級ExecutorService
的方法,在指定的延遲後執行Runnable
或Callable
任務,此外,接口定義了scheduleAtFixedRate
和scheduleWithFixedDelay
,它們以定義的間隔重複執行指定的任務。多線程
java.util.concurrent
中的大多數執行器實現都使用由工做線程組成的線程池,這種線程與它執行的Runnable
和Callable
任務分開存在,一般用於執行多個任務。
使用工做線程能夠最小化因爲建立線程而帶來的開銷,線程對象使用大量內存,在大型應用程序中,分配和釋放許多線程對象會產生大量的內存管理開銷。
一種常見類型的線程池是固定線程池,這種類型的池始終具備指定數量的線程,若是一個線程在它仍在使用時以某種方式被終止,它將自動被一個新線程替換,任務經過內部隊列提交到池中,當活動任務多於線程時,該隊列將保存額外的任務。
固定線程池的一個重要優勢是使用它的應用程序能夠優雅地降級,要理解這一點,請考慮一個Web服務器應用程序,其中每一個HTTP請求都由一個單獨的線程處理。若是應用程序只是爲每一個新的HTTP請求建立一個新線程,而且系統接收的請求數量超過了能夠當即處理的數量,當全部這些線程的開銷超過系統容量時,應用程序將忽然中止響應全部請求。因爲能夠建立的線程數量有限制,應用程序不會像HTTP請求進入時那樣快地爲它們提供服務,而是以系統可以承受的最快速度爲它們提供服務。
建立使用固定線程池的執行器的一種簡單方法是在java.util.concurrent.Executors中調用newFixedThreadPool工廠方法,該類還提供如下工廠方法:
ScheduledExecutorService
版本。若是上述工廠方法提供的執行器均沒法知足你的需求,構造java.util.concurrent.ThreadPoolExecutor或java.util.concurrent.ScheduledThreadPoolExecutor的實例將爲你提供額外選項。
fork/join框架是ExecutorService
接口的一個實現,可幫助你利用多個處理器,它專爲能夠遞歸分解成小塊的工做而設計,目標是使用全部可用的處理能力來加強應用程序的性能。
與任何ExecutorService
實現同樣,fork/join框架將任務分配給線程池中的工做線程,fork/join框架是不一樣的,由於它使用了工做竊取算法,沒有事情可作的工做線程能夠從仍然忙碌的其餘線程中竊取任務。
fork/join框架的中心是ForkJoinPool類,它是AbstractExecutorService
類的擴展,ForkJoinPool
實現了核心工做竊取算法,能夠執行ForkJoinTask進程。
使用fork/join框架的第一步是編寫執行工做片斷的代碼,你的代碼應相似於如下僞代碼:
if (個人工做部分足夠小) 直接作這項工做 else 把個人工做分紅兩塊 調用這兩塊並等待結果
將此代碼包裝在ForkJoinTask
子類中,一般使用其更專業的類型之一,RecursiveTask(能夠返回結果)或RecursiveAction。
在ForkJoinTask
子類準備就緒後,建立表示要完成的全部工做的對象,並將其傳遞給ForkJoinPool
實例的invoke()
方法。
爲了幫助你瞭解fork/join框架的工做原理,請考慮如下示例,假設你想模糊圖像,原始源圖像由整數數組表示,其中每一個整數包含單個像素的顏色值,模糊的目標圖像也由與源相同大小的整數數組表示。
經過一次一個像素地處理源數組來完成模糊,將每一個像素與其周圍像素進行平均(對紅色、綠色和藍色組件進行平均),並將結果放置在目標數組中,因爲圖像是大型數組,所以此過程可能須要很長時間,經過使用fork/join框架實現的算法,你能夠利用多處理器系統上的併發處理,這是一個可能的實現:
public class ForkBlur extends RecursiveAction { private int[] mSource; private int mStart; private int mLength; private int[] mDestination; // Processing window size; should be odd. private int mBlurWidth = 15; public ForkBlur(int[] src, int start, int length, int[] dst) { mSource = src; mStart = start; mLength = length; mDestination = dst; } protected void computeDirectly() { int sidePixels = (mBlurWidth - 1) / 2; for (int index = mStart; index < mStart + mLength; index++) { // Calculate average. float rt = 0, gt = 0, bt = 0; for (int mi = -sidePixels; mi <= sidePixels; mi++) { int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1); int pixel = mSource[mindex]; rt += (float)((pixel & 0x00ff0000) >> 16) / mBlurWidth; gt += (float)((pixel & 0x0000ff00) >> 8) / mBlurWidth; bt += (float)((pixel & 0x000000ff) >> 0) / mBlurWidth; } // Reassemble destination pixel. int dpixel = (0xff000000 ) | (((int)rt) << 16) | (((int)gt) << 8) | (((int)bt) << 0); mDestination[index] = dpixel; } } ...
如今,你實現抽象的compute()
方法,該方法能夠直接執行模糊或將其拆分爲兩個較小的任務,簡單的數組長度閾值有助於肯定是執行仍是拆分工做。
protected static int sThreshold = 100000; protected void compute() { if (mLength < sThreshold) { computeDirectly(); return; } int split = mLength / 2; invokeAll(new ForkBlur(mSource, mStart, split, mDestination), new ForkBlur(mSource, mStart + split, mLength - split, mDestination)); }
若是之前的方法在RecursiveAction
類的子類中,那麼將任務設置爲在ForkJoinPool
中運行是很簡單的,涉及如下步驟:
建立一個表明要完成的全部工做的任務。
// source image pixels are in src // destination image pixels are in dst ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
建立將運行任務的ForkJoinPool
。
ForkJoinPool pool = new ForkJoinPool();
運行任務。
pool.invoke(fb);
有關完整源代碼(包括建立目標圖像文件的一些額外代碼),請參閱ForkBlur示例。
除了使用fork/join框架來實如今多處理器系統上同時執行任務的自定義算法(例如ForkBlur.java
示例),Java SE中已經使用fork/join框架實現了一些一般有用的功能,在Java SE 8中引入的一種這樣的實現被java.util.Arrays類用於其parallelSort()
方法,這些方法相似於sort()
,但經過fork/join框架利用併發性。在多處理器系統上運行時,大型數組的並行排序比順序排序更快,可是,這些方法如何利用fork/join框架超出了Java教程的範圍,有關此信息,請參閱Java API文檔。
fork/join框架的另外一個實現由java.util.streams
包中的方法使用,這是Project Lambda計劃用於Java SE 8版本的一部分,有關更多信息,請參閱Lambda表達式部分。