重點是那個病毒掃描程序的例子,認真看三遍。本文花了四個小時。java
GitHub代碼歡迎star。git
小白認爲學習語言最好的方式就是模仿、思考別人爲何這麼寫。結合栗子效果更好,也能記住知識點。github
Executors類容許建立線程池並返回ExecutorService
對象,執行器提供了將任務提交與對任務進行解耦的標準方法,除了對基本的線程生命週期提供支持外,窒息功能其還提供統計收集,應用管理及監控方面的功能。這一切都基於 生產者-消費者模式。 使用這種設計模式能夠對大型併發應用程序很好的進行擴展。segmentfault
使用這種服務對象,能夠運行Runnable和Callable
類的實力,你只須要作的是提交任務給服務對象就能夠。ExecutorService
會從線程池中選擇線程,並將Runnable對象提交給線程任務。當任務結束時,線程並不會銷燬 ,而是返回到線程池中繼續執行後續的其餘任務,這樣能夠 避免建立和銷燬線程帶來的額外開銷設計模式
一、newFixedTHreadPool
方法可以建立固定大小的線程池。線程池中的線程將被用來處理任務請求,若是線程處於空閒狀態,線程不會銷燬,而是會被存放線程池中一段不肯定的是將網絡
二、newCachedThreadPool
,使用該方法建立的線程池中的線程會在空閒60秒以後自動銷燬,架構
三、newSingleThreadExecutor
該方法僅建立一個線程,當任務結束後不會銷燬而是用於處理其餘任務。對於多個任務同時請求,則使用隊列來維護全部待處理的請求。隨後會順序執行。併發
四、newScheduledThreadPool
能夠把它看做是java.util.Timer的替代品,該方法建立固定大小的線程池用來調度執行任務,並返回一個ScheduledExecutorService
對象,該對象提供了若干個方法用於執行任務的調度執行。app
有時建立可在必定時間延遲後執行的線程,能夠設置一個報警器在一段時間事後報警。在某些狀況下,你也但願以 必定的頻率或固定時間間隔反覆執行線程。
好比病毒掃描,你可使用newScheduledThreadPool
類實現的執行器服務,每24小時運行一次病毒掃描。若是有多個磁盤或大容量的磁盤須要掃描,將掃描的任務分解爲多個單元。讓每一個單元掃描某個特定的磁盤。dom
另外一凸顯此服務很實用的應用場景是新聞聚合器。聚合器從多個新聞源收集最新新聞,並將它們排列在客戶端以供閱讀,多個數據源獲取能夠併發執行,而這根據目標數據源的網絡情況,花費的時間會不同。客戶端和數據源的同步會週期性地執行。若是這樣的同步操做頻率很高,新的同步操做和當前正在執行的操做就有可能出現重疊。在這種狀況下,最好給每次任務的執行設固定的時間間隔,ScheduledExecutosService
能夠幫你實現這樣的需求。
一、ScheduledExecutorService
類提供了名爲schedule的方法用於設定任務的將來執行。schedule方法有兩個重載版本:
//Creates and executes a ScheduledFuture that becomes enabled after the given delay. <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) //Creates and executes a one-shot action that becomes enabled after the given delay. ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
schedule方法接收三個參數:Callable和Runnable接口、延遲時間以及時間單位。該方法安排由 Callable和Runnable
指定的任務在給定的延遲時間後執行。時間單位 由該方法的第三個參數指定。方法會返回一個Future對象給調用方。
二、除了這個簡單的延遲執行以外,ScheduledExecutorService類還提供了scheduleAtFixedRate方法,該任務能夠指定任務按照必定的頻率執行。
//Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period; //that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
第一次執行發生在給定的延遲以後,後續執行發生在「延遲+固定時間」,「延遲+2*固定週期」,依次類推,這種方法能夠用於病毒掃描
三、scheduleWithFiedDelay
方法在給定延遲以後第一次執行任務。以後按照固定好的時間間隔執行,時間間隔遞歸你覺得本次任務運行到下一次任務的開始。這類調度能夠用於新聞聚合應用。
//Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next. ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
/** * Created by guo on 2018/2/16. * 演示任務調度執行 * 需求: * 如何讓任務以必定的頻率執行。 * 一、該應用是以固定頻率執行的病毒掃描程序。 * 二、當掃描開始時,程序彈出窗口以顯示掃描進度,當磁盤上全部文件被掃描以後,任務會中止。 * 三、每次掃描都須要不一樣的時間,經過讓線程隨機睡眠一段時間來模擬這個過程。 * 四、掃描結束以後,狀態窗口會被關閉,知道下次掃描纔會彈出, */ public class VirusScanner { private static JFrame appFrame; private static JLabel statusString; private int scanNumber = 0; //一、調用Executors類的newScheduledThreadPool方法來建立線程池。 private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); private static final GregorianCalendar calendar = new GregorianCalendar(); private static VirusScanner app = new VirusScanner(); /** * scanDisk方法執行實際的掃描工做 */ public void scanDisk() { //二、使用線程池中的線程來解決多重併發掃描。 final Runnable scanner = new Runnable() { @Override public void run() { try { //將狀態窗口顯示給用戶 appFrame.setVisible(true); scanNumber++; Calendar cal = Calendar.getInstance(); //顯示掃描數以及掃描開始時間,接下來,讓當前線程隨機睡一段時間。 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.MEDIUM); statusString.setText(" Scan" + scanNumber + " started at" + df.format(cal.getTime())); //常數1000是用來確保窗口至少顯示1秒。在實際程序中,病毒掃描代碼會放在sleep語句所在的位置。 //讓線程休眠是僞裝病毒掃描持續一段時間, //當線程從休眠中喚醒時,咱們隱藏了窗口,這讓用戶感受當前一輪已經結束。 //題外話1:請卸載國產360,QQ管家,小白能夠無視。須要的組件能夠下載綠色版。(明明是一個開源軟件,你卻說那高危險。明明是https://www.github.com開頭。) //題外話2:感謝 架構@奇虎360,@江湖人稱小白哥。謝謝你的心意,能力沒到那,你還不能成爲我職業生涯的第一位貴人。騷年,加油吧,越努力,越幸運。 Thread.sleep(1000 + new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } }; //重點:三、使用以前建立的調度器來讓掃描程序以固定頻率執行。 // a、掃描任務在最初的一秒延遲以後會以每隔15秒的頻率運行 // b、調用器會返回一個Future對象,用於以後取消掃描任務。 // c、爲了可以進行取消操做,建立另外一個匿名線程。 // d、如下代碼全部時間單位爲秒,目前只是模擬的效果。 // e、在實際應用中,病毒掃描應當天天或每幾小時執行一次 final ScheduledFuture<?> scanManager = scheduler.scheduleAtFixedRate(scanner, 1, 15, TimeUnit.SECONDS); /** * 匿名線程 * 這個線程只在60秒延遲以後運行一次,模擬會以一分鐘的總時間週期執行 * 每隔15秒,病毒掃描狀態窗口會彈出,而且顯示請留1秒,或更長時間。 */ scheduler.schedule(new Runnable() { @Override public void run() { //四、取消病毒掃描任務,並關閉調度器和狀態窗口 scanManager.cancel(true); scheduler.shutdown(); appFrame.dispose(); } }, 60, TimeUnit.SECONDS); } }
/** * 不是重點的main方法: * 建立狀態窗口、設置並調用scanDisk方法。 * 注意:主線程會在以後馬上結束,而在scanDisk方法中建立的線程會在接下來一分鐘內繼續運行。 */ public static void main(String[] args) { appFrame = new JFrame(); Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize(); appFrame.setSize(400, 70); appFrame.setLocation(dimension.width / 2 - appFrame.getWidth() / 2, dimension.height / 2 - appFrame.getHeight() / 2); statusString = new JLabel(); appFrame.add(statusString); appFrame.setVisible(false); app.scanDisk(); }
以前已經學瞭如何將任務提交給執行器當即執行、延遲以及週期性的運行 (計算年銷售額) 還了解到執行器能夠提供並維護多個線程併發的執行任務 (模擬可取消任務的股票交易處理程序) 。在某些狀況下,當提交多個任務給執行器,你可能但願處理任意以結束任務的結果,而不像等到每一個任務都執行結束。目前只用過執行器的get方法會等待任務結束。當任務提交時,能夠建立循環來獲取每一個計算結果,代碼以下:
for(Future<T> result : results) { result.get(); }
這樣就能夠順序的獲取結果,但若是某個特定的任務須要長時間才能結束,那麼當前的get調用會一直阻塞.在這種狀況下,即便其餘任務已經提早完成,也沒法獲取結果,爲了解決這個問題,可使用ExecutorCompletionService
類,該類會檢測提交給執行器的任務,經過take方法,能夠一個個地獲取到任務執行的結果。
待續...