模擬病毒掃描程序(Executors、ScheduledExecutorService類)

Executors類

重點是那個病毒掃描程序的例子,認真看三遍。本文花了四個小時。java

GitHub代碼歡迎star。git

小白認爲學習語言最好的方式就是模仿思考別人爲何這麼寫。結合栗子效果更好,也能記住知識點github

Executors類容許建立線程池並返回ExecutorService對象,執行器提供了將任務提交與對任務進行解耦的標準方法,除了對基本的線程生命週期提供支持外,窒息功能其還提供統計收集,應用管理及監控方面的功能。這一切都基於 生產者-消費者模式。 使用這種設計模式能夠對大型併發應用程序很好的進行擴展。segmentfault

使用這種服務對象,能夠運行Runnable和Callable類的實力,你只須要作的是提交任務給服務對象就能夠。ExecutorService會從線程池中選擇線程,並將Runnable對象提交給線程任務。當任務結束時,線程並不會銷燬 ,而是返回到線程池中繼續執行後續的其餘任務,這樣能夠 避免建立和銷燬線程帶來的額外開銷設計模式

Executors類有許多靜態方法可用來建立線程池:

一、newFixedTHreadPool方法可以建立固定大小的線程池。線程池中的線程將被用來處理任務請求,若是線程處於空閒狀態,線程不會銷燬,而是會被存放線程池中一段不肯定的是將網絡

二、newCachedThreadPool,使用該方法建立的線程池中的線程會在空閒60秒以後自動銷燬,架構

三、newSingleThreadExecutor該方法僅建立一個線程,當任務結束後不會銷燬而是用於處理其餘任務。對於多個任務同時請求,則使用隊列來維護全部待處理的請求。隨後會順序執行。併發

四、newScheduledThreadPool能夠把它看做是java.util.Timer的替代品,該方法建立固定大小的線程池用來調度執行任務,並返回一個ScheduledExecutorService對象,該對象提供了若干個方法用於執行任務的調度執行。app

建立線程池以進行任務調度

有時建立可在必定時間延遲後執行的線程,能夠設置一個報警器在一段時間事後報警。在某些狀況下,你也但願以 必定的頻率或固定時間間隔反覆執行線程。
好比病毒掃描,你可使用newScheduledThreadPool類實現的執行器服務,每24小時運行一次病毒掃描。若是有多個磁盤或大容量的磁盤須要掃描,將掃描的任務分解爲多個單元。讓每一個單元掃描某個特定的磁盤。dom

另外一凸顯此服務很實用的應用場景是新聞聚合器。聚合器從多個新聞源收集最新新聞,並將它們排列在客戶端以供閱讀,多個數據源獲取能夠併發執行,而這根據目標數據源的網絡情況,花費的時間會不同。客戶端和數據源的同步會週期性地執行。若是這樣的同步操做頻率很高,新的同步操做和當前正在執行的操做就有可能出現重疊。在這種狀況下,最好給每次任務的執行設固定的時間間隔,ScheduledExecutosService能夠幫你實現這樣的需求。

ScheduledExecutorService類

一、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方法,能夠一個個地獲取到任務執行的結果。

待續...

相關文章
相關標籤/搜索