參考:
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) 創建並執行在給定的初始延遲之后,以給定的時間間隔執行周期性動作。即在 |
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的用法,最后兩者結合使用,分析並解決可能出現的問題。
由於線程池的源碼還是很多的,這里就不具體的解釋了,想具體了解的朋友可以自己去查看源碼或者網上查看資料。
最后帶上源碼鏈接,有問題或者建議可以留言。
轉自: https://blog.csdn.net/u011315960/article/details/71422386