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)
创建并执行在给定的初始延迟之后,以给定的时间间隔执行周期性动作。即在 |
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