Java™ 教程(執行器)

執行器

在前面的全部示例中,由新的線程(由其Runnable對象定義)和線程自己(由Thread對象定義)完成的任務之間存在緊密的聯繫,這適用於小型應用程序,但在大型應用程序中,將線程管理和建立與應用程序的其他部分分開是有意義的,封裝這些函數的對象稱爲執行器,如下小節詳細描述了執行器。html

  • 執行器接口定義三個執行器對象類型。
  • 線程池是最多見的執行器實現類型。
  • Fork/Join是一個利用多個處理器的框架(JDK 7中的新增功能)。

執行器接口

java.util.concurrent包定義了三個執行器接口:java

  • Executor,一個支持啓動新任務的簡單接口。
  • ExecutorServiceExecutor的子接口,它添加了有助於管理生命週期的功能,包括單個任務和執行器自己。
  • ScheduledExecutorServiceExecutorService的子接口,支持未來和/或按期執行任務。

一般,引用執行器對象的變量被聲明爲這三種接口類型之一,而不是執行器類類型。git

Executor接口

Executor接口提供單個方法execute,旨在成爲常見線程建立語法的替代方法,若是rRunnable對象,而且eExecutor對象,則能夠替換github

(new Thread(r)).start();

算法

e.execute(r);

可是,execute的定義不太具體,低級別語法建立一個新線程並當即啓動它,根據Executor實現,execute可能會作一樣的事情,但更有可能使用現有的工做線程來運行r,或者將r放在隊列中以等待工做線程變爲可用(咱們將在線程池的部分中描述工做線程)。segmentfault

java.util.concurrent中的執行器實現旨在充分利用更高級的ExecutorServiceScheduledExecutorService接口,儘管它們也能夠與基本Executor接口一塊兒使用。api

ExecutorService接口

ExecutorService接口使用相似但更通用的submit方法補充execute,與execute同樣,submit接受Runnable對象,但也接受Callable對象,這容許任務返回一個值。submit方法返回一個Future對象,該對象用於檢索Callable返回值並管理CallableRunnable任務的狀態。數組

ExecutorService還提供了提交大量Callable對象的方法,最後,ExecutorService提供了許多用於管理執行器關閉的方法,爲了支持當即關閉,任務應該正確處理中斷服務器

ScheduledExecutorService接口

ScheduledExecutorService接口使用schedule補充其父級ExecutorService的方法,在指定的延遲後執行RunnableCallable任務,此外,接口定義了scheduleAtFixedRatescheduleWithFixedDelay,它們以定義的間隔重複執行指定的任務。多線程

線程池

java.util.concurrent中的大多數執行器實現都使用由工做線程組成的線程池,這種線程與它執行的RunnableCallable任務分開存在,一般用於執行多個任務。

使用工做線程能夠最小化因爲建立線程而帶來的開銷,線程對象使用大量內存,在大型應用程序中,分配和釋放許多線程對象會產生大量的內存管理開銷。

一種常見類型的線程池是固定線程池,這種類型的池始終具備指定數量的線程,若是一個線程在它仍在使用時以某種方式被終止,它將自動被一個新線程替換,任務經過內部隊列提交到池中,當活動任務多於線程時,該隊列將保存額外的任務。

固定線程池的一個重要優勢是使用它的應用程序能夠優雅地降級,要理解這一點,請考慮一個Web服務器應用程序,其中每一個HTTP請求都由一個單獨的線程處理。若是應用程序只是爲每一個新的HTTP請求建立一個新線程,而且系統接收的請求數量超過了能夠當即處理的數量,當全部這些線程的開銷超過系統容量時,應用程序將忽然中止響應全部請求。因爲能夠建立的線程數量有限制,應用程序不會像HTTP請求進入時那樣快地爲它們提供服務,而是以系統可以承受的最快速度爲它們提供服務。

建立使用固定線程池的執行器的一種簡單方法是在java.util.concurrent.Executors中調用newFixedThreadPool工廠方法,該類還提供如下工廠方法:

  • newCachedThreadPool方法使用可擴展線程池建立執行器,此執行器適用於啓動許多短時間任務的應用程序。
  • newSingleThreadExecutor方法建立一次執行單個任務的執行器。
  • 有幾個工廠方法是上述執行器的ScheduledExecutorService版本。

若是上述工廠方法提供的執行器均沒法知足你的需求,構造java.util.concurrent.ThreadPoolExecutorjava.util.concurrent.ScheduledThreadPoolExecutor的實例將爲你提供額外選項。

Fork/Join

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中運行是很簡單的,涉及如下步驟:

  1. 建立一個表明要完成的全部工做的任務。

    // source image pixels are in src
    // destination image pixels are in dst
    ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
  2. 建立將運行任務的ForkJoinPool

    ForkJoinPool pool = new ForkJoinPool();
  3. 運行任務。

    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表達式部分。


上一篇:Lock對象

下一篇:原子變量

相關文章
相關標籤/搜索