ScheduledExecutorService用法


參考:

https://aflyun.blog.csdn.net/article/details/78202269

https://wangmaoxiong.blog.csdn.net/article/details/80840598

https://blog.csdn.net/ma969070578/article/details/82863477

 

 

 

 

ScheduledThreadPoolExecutor 中ScheduleAtFixedRate 和 ScheduleWithFixedDelay方法講解

java 中ScheduledExecutorService接口是基於線程池設計的定時任務類,每個調度任務都會分配到線程池中的一個線程去執行,也就是說,任務是並發執行,互不影響。
其中的一個實現類是ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor的uml類圖關系如下:

這里寫圖片描述

(1)>ScheduledThreadPoolExecutor實現ScheduledExecutorService接口,實現了一些定時任務處理的方法。

(2)>ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,可以通過線程池進行任務的管理和調度。

下面介紹ScheduledThreadPoolExecutor中實現ScheduledExecutorService接口最常用的調度的兩個方法ScheduleAtFixedRate 和 ScheduleWithFixedDelay。

一、scheduleAtFixedRate方法
1:方法簡介

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

上面的四個參數進行講解:
第一個command參數是任務實例,
第二個initialDelay參數是初始化延遲時間,
第三個period參數是間隔時間,
第四個unit參數是時間單元。

2:代碼實例
(1):當任務實例commod執行的時間小於間隔時間period情況

public class TestExecutor { private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); public static void main(String[] args) { } /** * 進行scheduleAtFixedRate測試 */ public static void testFixedRate(){ executor.scheduleAtFixedRate(new myRun(), 5, 5, TimeUnit.SECONDS); } static class myRun implements Runnable{ @Override public void run() { System.out.println("----測試開始--------"+ new Date().toLocaleString()); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("---休眠3秒后, 處理結束--------"+new Date().toLocaleString()); } } }


運行結果:

----測試開始--------2017-10-11 11:38:38 #第一次執行 ---休眠3秒后, 處理結束--------2017-10-11 11:38:41 #第一次任務處理,花費3秒 #第二次執行時間是第一次時間 + period 即38 + 5 = 43; ----測試開始--------2017-10-11 11:38:43 ---休眠3秒后, 處理結束--------2017-10-11 11:38:46 ----測試開始--------2017-10-11 11:38:48 ---休眠3秒后, 處理結束--------2017-10-11 11:38:51 ----測試開始--------2017-10-11 11:38:53


(2):當任務實例commod執行的時間大於間隔時間period情況
修改 Thread.sleep(3000); 為 Thread.sleep(6000);,執行查看運行結果!

運行結果:

----測試開始--------2017-10-11 11:41:22 #第一次執行時間 ---休眠3秒后, 處理結束--------2017-10-11 11:41:28 # 任務處理6秒,即 22+6 = 28 #第二次執行時間 == 上一次處理結束時間,因為任務處理時間大於period間隔時間 ----測試開始--------2017-10-11 11:41:28 ---休眠3秒后, 處理結束--------2017-10-11 11:41:34 ----測試開始--------2017-10-11 11:41:34


3:總結
ScheduleAtFixedRate 每次執行時間為上一次任務開始起向后推一個時間間隔。分為兩種情況:
(1)若command執行的時間小於period若每次執行時間為 :initialDelay, initialDelay+period, initialDelay+2*period, …;

(2)若command執行的時間大於period,則command執行完,下一次任務將立即執行!下即下一次任務不會按照預期的時間間隔執行,每次執行時間為 :initialDelay, initialDelay+taskExecutorTIme, initialDelay+2*taskExecutorTIme, …;

taskExecutorTIme為任務執行的時間!

二、scheduleWithFixedDelay

1:方法簡介

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit


上面的四個參數進行講解:
第一個command參數是任務實例,
第二個initialDelay參數是初始換延遲時間,
第三個delay參數是延遲間隔時間,
第四個unit參數是時間單元

2:代碼實例

(1):當任務實例commod執行的時間小於延遲間隔時間delay情況

public class TestExecutor { private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); public static void main(String[] args) { testFixedDelay(); } public static void testFixedDelay(){ executor.scheduleWithFixedDelay(new myRun(), 5, 5, TimeUnit.SECONDS); } static class myRun implements Runnable{ @Override public void run() { System.out.println("----測試開始--------"+ new Date().toLocaleString()); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("---休眠3秒后, 處理結束--------"+new Date().toLocaleString()); } } }


運行結果:

----測試開始--------2017-10-11 11:59:02 #第一次執行時間 ---休眠3秒后, 處理結束--------2017-10-11 11:59:05 #任務處理的時間,3秒 #第二次執行的時間 == 第一次任務開始時間+任務處理時間+delay延遲時間 #即 10 == 02 + 3秒 + 5秒 ----測試開始--------2017-10-11 11:59:10 ---休眠3秒后, 處理結束--------2017-10-11 11:59:13 ----測試開始--------2017-10-11 11:59:18 ---休眠3秒后, 處理結束--------2017-10-11 11:59:21 ....


(2):當任務實例commod執行的時間大於延遲間隔時間delay情況
將Thread.sleep(3000); 修改為 Thread.sleep(6000);
運行結果:

----測試開始--------2017-10-11 12:02:48 #第一次任務執行開始時間 ---休眠6秒后, 處理結束--------2017-10-11 12:02:54 #任務處理的時間 ,6秒 #第二次任務執行開始時間 == 第一次任務執行開始時間 + 任務處理的時間 + delay延遲時間 #即 59 == 48 + 6 + 5 ----測試開始--------2017-10-11 12:02:59 ---休眠6秒后, 處理結束--------2017-10-11 12:03:05 ----測試開始--------2017-10-11 12:03:10 ......


3:總結
不管任務command執行的時間是多長,下一次任務的執行時間都是上一次任務執行完后在等待延遲間隔delay時間后執行下一次任務。
ScheduleWithFixedDelay 每次執行時間為上一次任務結束起向后推一個時間間隔,即每次執行時間為:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay。

三、參考
https://www.ibm.com/developerworks/cn/java/j-lo-taskschedule/

 

 

 

 

 

 

 

ScheduledExecutorService 延遲 / 周期執行線程池


ScheduledExecutorService 簡述

1、public interface ScheduledExecutorService extends ExecutorService 延遲或定期執行任務。 

2、schedule 方法使用各種延遲創建任務,並返回一個可用於取消或檢查執行的任務對象

3、scheduleAtFixedRate 和 scheduleWithFixedDelay 方法創建並執行某些在取消前一直定期運行的任務

4、所有的 schedule 方法都接受相對延遲和周期作為參數,而不是絕對的時間或日期

5、SheduleExecutorService 是JDK 1.5出來的,比以前的 Timer 性能好

Method  Description
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

創建並執行在給定延遲后啟用的單次操作。

ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

創建並執行在給定延遲后啟用的單次操作。

ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

創建並執行在給定的初始延遲之后,以給定的時間間隔執行周期性動作。即在 initialDelay 初始延遲后,initialDelay+period 執行第一次,initialDelay + 2 * period  執行第二次,依次類推。

ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

創建並執行在給定的初始延遲之后首先啟用的定期動作,隨后上一個執行的終止和下一個執行的開始之間給定的延遲。

對象創建方式

1、此實例最快捷的方式是使用 Executors 工具來創建:

ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 創建一個線程池,它可安排在給定延遲后運行任務或者定期地執行任務。 corePoolSize - 池中所保存的線程數,即使線程是空閑的也包括在內。
ScheduledExecutorService newSingleThreadScheduledExecutor() 創建一個單線程執行程序,它可安排在給定延遲后運行命令或者定期地執行任務。可保證順序地執行各個任務,並且在任意給定的時間不會有多個線程是活動的。 同樣這是一個無界的任務隊列,即雖然線程只有一個,但是新增的任務會在隊列中排隊等待執行

2、此外除了使用 Executors 創建之外,推薦使用 ScheduledExecutorService 的實現類 ScheduledThreadPoolExecutor

schedule + Runnable 延遲執行任務

1、ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit) :創建並執行在給定延遲后啟用的一次性操作。 

2、參數:command - 要執行的任務;delay - 從現在開始延遲執行的時間;unit - 延遲參數的時間單位 

 

 

schedule + Callable 延遲執行任務

1、ScheduledFuture<V> schedule(Callable<V> callable, long delay,TimeUnit unit):創建並執行在給定延遲后啟用的 一次性操作

2、參數:callable - 要執行的功能;delay - 從現在開始延遲執行的時間;unit - 延遲參數的時間單位 

 

scheduleAtFixedRate 周期性執行任務

1、ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay, long period, TimeUnit unit):創建並執行一個在給定初始延遲后首次啟用的定期操作,后續操作具有給定的周期;

2、在 initialDelay 后開始執行,然后在 initialDelay+period 后執行,接着在 initialDelay + 2 * period 后執行,依此類推。意思是下一次執行任務的時間與任務執行過程花費的時間無關,只與period有關!

3、如果此任務的任何一個執行要花費比其周期更長的時間,則將推遲后續執行,但不會同時執行。 

4、如果任務的任何一個執行遇到異常,則后續執行都會被取消。否則,只能通過執行程序的取消或終止方法來終止該任務。

5、參數:command - 要執行的任務;initialDelay - 首次執行的延遲時間;period - 連續執行之間的周期;unit - initialDelay 和 period 參數的時間單位 

import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * Created by Administrator on 2018/6/28 0028.
 * 自定義任務
 */
public class MyRunable implements Runnable {
    private AtomicInteger atomicInteger = null;
    private Random random = null;
    public MyRunable() {
        atomicInteger = new AtomicInteger(0);
        random = new Random();
    }
    @Override
    public void run() {
        try {
            String threadName = Thread.currentThread().getName();
            System.out.println("1-任務執行開始:" + new Date() + ":" + threadName);
            /**使用隨機延時[0-3]秒來模擬執行任務*/
            int sleepNumber = random.nextInt(5);
            TimeUnit.SECONDS.sleep(sleepNumber);
            if (atomicInteger.getAndAdd(1) == 3) {
                /** 故意拋出一個異常*/
                int error = 10 / 0;
            }
            System.out.println("2-任務執行完畢:" + new Date() + ":" + threadName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

1)上面 run 方法中如果沒有去捕獲異常,即沒有 try-catch 時,一旦拋出異常卻沒有捕獲到,則后續的定時任務不會再繼續執行,這就和 Timer 是一樣的,可以參考《Java 定時器Timer與TimeTask》

2)try-catch 時一定要完全捕獲到異常,例如果里面拋的是空指針異常,你捕獲的卻是數組下標越界異常,則仍然會中斷計划任務,后續的任務仍然不再執行,所以建議:1)try-catch 包含 run 方法中的所有代碼,2)catch 異常最外圍使用 "Exception",以免某些異常未捕獲而導致計划失敗。

import java.util.Date;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * Created by Administrator on 2018/6/28 0028.
 */
public class Test {
    public static void main(String[] args) {
        /**使用Executors工具快速構建對象*/
        ScheduledExecutorService scheduledExecutorService =
                Executors.newSingleThreadScheduledExecutor();
        System.out.println("3秒后開始執行計划線程池服務..." + new Date());
        /**每間隔4秒執行一次任務*/
        scheduledExecutorService.scheduleAtFixedRate(new MyRunable(),
                3, 4, TimeUnit.SECONDS);
    }
}

 

 

scheduleWithFixedDelay 周期性執行任務

1、ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):創建並執行一個在給定初始延遲后首次啟用的定期操作,隨后,在每一次執行終止和下一次執行開始之間都存在給定的延遲。

2、與 scheduleFixedDelay 區別僅僅在於前后兩次任務執行的時間間隔不同而已

import java.util.Date;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * Created by Administrator on 2018/6/28 0028.
 */
public class Test {
    public static void main(String[] args) {
        /**使用Executors工具快速構建對象*/
        ScheduledExecutorService scheduledExecutorService =
                Executors.newSingleThreadScheduledExecutor();
        System.out.println("3秒后開始執行計划線程池服務..." + new Date());
        /**每間隔4秒執行一次任務*/
        scheduledExecutorService.scheduleWithFixedDelay(new MyRunable(),
                3, 4, TimeUnit.SECONDS);
    }
}

 

在 scheduleFixedDelay 的例子上僅改變上面一行代碼,運行結果如下

try-catch 時一定要完全捕獲到異常,例如果里面拋的是空指針異常,你捕獲的卻是數組下標越界異常,則仍然會中斷計划任務,后續的任務仍然不再執行,所以建議:1)try-catch 包含 run 方法中的所有代碼,2)catch 異常最外圍使用 "Exception",以免某些異常未捕獲而導致計划失敗。

 

 

 

 

 

ScheduledExecutorService的使用

前言

android的線程池主要有四個:

newSingleThreadExecutor:單線程池,同時只有一個線程在跑。
newCachedThreadPool() :回收型線程池,可以重復利用之前創建過的線程,運行線程最大數是Integer.MAX_VALUE。
newFixedThreadPool() :固定大小的線程池,跟回收型線程池類似,只是可以限制同時運行的線程數量

哎???第四個是什么來着,不知道你有沒有想起來,反正我是沒記住,結果面試的時候就栽了個跟頭。

正文

第四種線程池是ScheduledExecutorService,我平時沒有用過,他的最大優點除了線程池的特性以外,可以實現循環或延遲任務。

ScheduledExecutorService 和 Timer 的區別

Timer的內部只有一個線程,如果有多個任務的話就會順序執行,這樣我們的延遲時間和循環時間就會出現問題。

ScheduledExecutorService是線程池,所以就不會出現這個情況,在對延遲任務和循環任務要求嚴格的時候,就需要考慮使用ScheduledExecutorService了。

ScheduledExecutorService的用法主要有三個:

public class MainActivity extends AppCompatActivity {
 
    // 通過靜態方法創建ScheduledExecutorService的實例
    private ScheduledExecutorService mScheduledExecutorService = Executors.newScheduledThreadPool(4);
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // 延時任務
        mScheduledExecutorService.schedule(threadFactory.newThread(new Runnable() {
            @Override
            public void run() {
                Log.e("lzp", "first task");
            }
        }), 1, TimeUnit.SECONDS);
 
        // 循環任務,按照上一次任務的發起時間計算下一次任務的開始時間
        mScheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Log.e("lzp", "first:" + System.currentTimeMillis() / 1000);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
 
        // 循環任務,以上一次任務的結束時間計算下一次任務的開始時間
        mScheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                Log.e("lzp", "scheduleWithFixedDelay:" + System.currentTimeMillis() / 1000);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
 
 
    }
}

 

 

在代碼中已經有注釋說明這三個方法的用法,重點是scheduleAtFixedRate和scheduleWithFixedDelay的區別,下面看具體看一下Log信息:

首先是scheduleAtFixedRate:

這里寫圖片描述

從log上看,我們的循環任務嚴格按照每一秒發起一次,sleep(3000)對於任務的開啟是沒有影響的,也就是以上一個任務的開始時間 + 延遲時間 = 下一個任務的開始時間。

然后是scheduleWithFixedDelay:

這里寫圖片描述

從log上看,每一個任務的時間間隔是4秒,而不是我們設置的間隔1秒,任務要耗時3秒,兩個時間相加正好是4秒,那么之前代碼注釋的解釋就說的通了:以上一次任務的結束時間 + 延遲時間 = 下一次任務的開始時間

擴展:ThreadFactory

因為在線程池的構造方法中,有ThreadFactory參數,所以就來簡單介紹一個ThreadFactory:

線程工廠,可以對傳入的Runnable進行加工操作,典型的工廠模式,需要實現newThread(@NonNull Runnable r) 方法返回加工后的線程。

看一下具體的代碼:

private ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(@NonNull final Runnable r) {
            return new Thread() {
                @Override
                public void run() {
                    r.run();
                    Log.e("lzp", "嘿嘿嘿");
                }
            };
        }
    };

 

 

例如上面的代碼,我在參數Runnable對象的run方法后,增加了一句log打印。

然后修改MainActivity的調用:

mScheduledExecutorService.schedule(threadFactory.newThread(new Runnable() {
            @Override
            public void run() {
                Log.e("lzp", "first task");
            }
        }), 1, TimeUnit.SECONDS);

 

 

這里面我傳入ThreadFactory加工過后的Runnable,我們推斷,最后應該會打印兩句:first task 和 嘿嘿嘿,運行一下看看是不是符合我們的期望:

這里寫圖片描述

果然結果沒有讓我們失望。

然后我有了一個想法,我要給線程池所有的線程都加工一下:

private ScheduledExecutorService mScheduledExecutorService = Executors.newScheduledThreadPool(4, new ThreadFactory() {
        @Override
        public Thread newThread(@NonNull Runnable r) {
           return new Thread(r){
                @Override
                public void run() {
                    Log.e("lzp", "newThread");
                    super.run();
                    Log.e("lzp", "newThread over");
                }
            };
        }
    });

 

 

我在Runnable參數的run方法前后增加了打印,於是我迫不及待的運行了app:

這里寫圖片描述

經過短暫的喜悅,我發現我的希望落空了,出現了兩個問題:

1、Log.e(“lzp”, “newThread over”) 這一句為什么沒有執行???
2、為什么ThreadFactory.newThread() 只執行了四次。

帶着疑問我開始查看源碼,經過仔細的思考,找到了問題的原因,這里就簡單解釋一下:

1、線程池只會創建最大並發數(corePoolSize)的Worker對象。

2、Worker對象內部包含了Thread和Runnable信息。

3、添加任務時:
如果Worker沒有到最大值,就創建Worker(通過ThreadFactory的newThread新建了Thread,所以Thread的數量等於Worker數量);
如果已經是最大,就把任務放到隊列(Quene)中。

3、任務執行中:
如果Worker的Runnable不為空,執行Runnable,執行結束,Runnable設置為空;
如果Worker的Runnable為空,從隊列中取出任務執行。

4、Runnable運行結束,線程池內部直接退出了線程。

弄懂了內部的機制,剛才的問題就全都弄明白了,還發現android的設計者是不希望我們通過這種方法去影響Runnable的實現方法,但是可以去改變線程Thread對象的屬性,例如優先級等等。

總結

這一篇我們先去弄懂了ScheduledExecutorService的特性和用法,然后了解了ThreadFactory的用法,最后兩者結合使用,分析並解決可能出現的問題。

由於線程池的源碼還是很多的,這里就不具體的解釋了,想具體了解的朋友可以自己去查看源碼或者網上查看資料。

最后帶上源碼鏈接,有問題或者建議可以留言。

Demo下載鏈接

轉自: https://blog.csdn.net/u011315960/article/details/71422386

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM