執行器
在前面的所有示例中,由新的線程(由其Runnable對象定義)和線程本身(由Thread對象定義)完成的任務之間存在緊密的聯系,這適用于小型應用程序,但在大型應用程序中,將線程管理和創建與應用程序的其余部分分開是有意義的,封裝這些函數的對象稱為執行器,以下小節詳細描述了執行器。
執行器接口定義三個執行器對象類型。
線程池是最常見的執行器實現類型。
Fork/Join是一個利用多個處理器的框架(JDK 7中的新增功能)。
執行器接口java.util.concurrent包定義了三個執行器接口:
Executor,一個支持啟動新任務的簡單接口。
ExecutorService,Executor的子接口,它添加了有助于管理生命周期的功能,包括單個任務和執行器本身。
ScheduledExecutorService,ExecutorService的子接口,支持將來和/或定期執行任務。
通常,引用執行器對象的變量被聲明為這三種接口類型之一,而不是執行器類類型。
Executor接口Executor接口提供單個方法execute,旨在成為常見線程創建語法的替代方法,如果r是Runnable對象,并且e是Executor對象,則可以替換
(new Thread(r)).start();
為
e.execute(r);
但是,execute的定義不太具體,低級別語法創建一個新線程并立即啟動它,根據Executor實現,execute可能會做同樣的事情,但更有可能使用現有的工作線程來運行r,或者將r放在隊列中以等待工作線程變為可用(我們將在線程池的部分中描述工作線程)。
java.util.concurrent中的執行器實現旨在充分利用更高級的ExecutorService和ScheduledExecutorService接口,盡管它們也可以與基本Executor接口一起使用。
ExecutorService接口ExecutorService接口使用類似但更通用的submit方法補充execute,與execute一樣,submit接受Runnable對象,但也接受Callable對象,這允許任務返回一個值。submit方法返回一個Future對象,該對象用于檢索Callable返回值并管理Callable和Runnable任務的狀態。
ExecutorService還提供了提交大量Callable對象的方法,最后,ExecutorService提供了許多用于管理執行器關閉的方法,為了支持立即關閉,任務應該正確處理中斷。
ScheduledExecutorService接口ScheduledExecutorService接口使用schedule補充其父級ExecutorService的方法,在指定的延遲后執行Runnable或Callable任務,此外,接口定義了scheduleAtFixedRate和scheduleWithFixedDelay,它們以定義的間隔重復執行指定的任務。
線程池java.util.concurrent中的大多數執行器實現都使用由工作線程組成的線程池,這種線程與它執行的Runnable和Callable任務分開存在,通常用于執行多個任務。
使用工作線程可以最小化由于創建線程而帶來的開銷,線程對象使用大量內存,在大型應用程序中,分配和釋放許多線程對象會產生大量的內存管理開銷。
一種常見類型的線程池是固定線程池,這種類型的池始終具有指定數量的線程,如果一個線程在它仍在使用時以某種方式被終止,它將自動被一個新線程替換,任務通過內部隊列提交到池中,當活動任務多于線程時,該隊列將保存額外的任務。
固定線程池的一個重要優點是使用它的應用程序可以優雅地降級,要理解這一點,請考慮一個Web服務器應用程序,其中每個HTTP請求都由一個多帶帶的線程處理。如果應用程序只是為每個新的HTTP請求創建一個新線程,并且系統接收的請求數量超過了可以立即處理的數量,當所有這些線程的開銷超過系統容量時,應用程序將突然停止響應所有請求。由于可以創建的線程數量有限制,應用程序不會像HTTP請求進入時那樣快地為它們提供服務,而是以系統能夠承受的最快速度為它們提供服務。
創建使用固定線程池的執行器的一種簡單方法是在java.util.concurrent.Executors中調用newFixedThreadPool工廠方法,該類還提供以下工廠方法:
newCachedThreadPool方法使用可擴展線程池創建執行器,此執行器適用于啟動許多短期任務的應用程序。
newSingleThreadExecutor方法創建一次執行單個任務的執行器。
有幾個工廠方法是上述執行器的ScheduledExecutorService版本。
如果上述工廠方法提供的執行器均無法滿足你的需求,構造java.util.concurrent.ThreadPoolExecutor或java.util.concurrent.ScheduledThreadPoolExecutor的實例將為你提供額外選項。
Fork/Joinfork/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表達式部分。
上一篇:Lock對象 下一篇:原子變量文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73062.html
摘要:在這個示例中我們使用了一個單線程線程池的。在延遲消逝后,任務將會并發執行。這是并發系列教程的第一部分。第一部分線程和執行器第二部分同步和鎖第三部分原子操作和 Java 8 并發教程:線程和執行器 原文:Java 8 Concurrency Tutorial: Threads and Executors 譯者:BlankKelly 來源:Java8并發教程:Threads和Execut...
Java? 教程 Java教程是為JDK 8編寫的,本頁面中描述的示例和實踐沒有利用在后續版本中引入的改進。 Java教程是希望使用Java編程語言創建應用程序的程序員的實用指南,其中包括數百個完整的工作示例和數十個課程,相關課程組被組織成教程。 覆蓋基礎知識的路徑 這些教程以書籍的形式提供,如Java教程,第六版,前往Amazon.com購買。 入門 介紹Java技術和安裝Java開發軟件并使用...
并發 計算機用戶想當然地認為他們的系統一次可以做不止一件事,他們設想他們可以繼續在文字處理器中工作,而其他應用程序則下載文件、管理打印隊列和流音頻,即使是單個應用程序通常也希望一次完成多個任務。例如,流式音頻應用程序必須同時從網絡上讀取數字音頻、解壓縮、管理回放并更新其顯示,甚至文字處理器應始終準備好響應鍵盤和鼠標事件,無論重新格式化文本或更新顯示有多繁忙,可以執行此類操作的軟件稱為并發軟件。 J...
摘要:并發教程原子變量和原文譯者飛龍協議歡迎閱讀我的多線程編程系列教程的第三部分。如果你能夠在多線程中同時且安全地執行某個操作,而不需要關鍵字或上一章中的鎖,那么這個操作就是原子的。當多線程的更新比讀取更頻繁時,這個類通常比原子數值類性能更好。 Java 8 并發教程:原子變量和 ConcurrentMap 原文:Java 8 Concurrency Tutorial: Synchroni...
高級并發對象 到目前為止,本課程重點關注從一開始就是Java平臺一部分的低級別API,這些API適用于非?;A的任務,但更高級的任務需要更高級別的構建塊,對于充分利用當今多處理器和多核系統的大規模并發應用程序尤其如此。 在本節中,我們將介紹Java平臺5.0版中引入的一些高級并發功能,大多數這些功能都在新的java.util.concurrent包中實現,Java集合框架中還有新的并發數據結構。 ...
摘要:未來的主要發布基于。在中調用函數支持從代碼中直接調用定義在腳本文件中的函數。下面的函數稍后會在端調用為了調用函數,你首先需要將腳本引擎轉換為。調用函數將結果輸出到,所以我們會首先看到輸出。幸運的是,有一套補救措施。 原文:Java 8 Nashorn Tutorial 譯者:飛龍 協議:CC BY-NC-SA 4.0 這個教程中,你會通過簡單易懂的代碼示例,來了解Nashorn Ja...
閱讀 2744·2021-11-19 09:40
閱讀 5294·2021-09-27 14:10
閱讀 2099·2021-09-04 16:45
閱讀 1462·2021-07-25 21:37
閱讀 2994·2019-08-30 10:57
閱讀 2981·2019-08-28 17:59
閱讀 1055·2019-08-26 13:46
閱讀 1408·2019-08-26 13:27