当前位置:   article > 正文

ScheduledExecutorService 使用

scheduledexecutorservice

ScheduledExecutorService,我平时没有用过,他的最大优点除了线程池的特性以外,可以实现循环或延迟任务。

ScheduledExecutorService,是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

需要注意,只有当调度任务来的时候,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。

先来个简单的延迟任务调用,看看效果,重点在后面

ScheduledExecutorService本身也提供了只运行一次的延迟任务方法schedule,只在延迟时间后 运行一次

  1. private static ScheduledExecutorService scheduler;
  2. public static void main(String[] args) throws Exception {
  3. scheduler = Executors.newScheduledThreadPool(5);
  4. System.out.println("main thread time : " + formatDateToString(new Date()));
  5. // 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
  6. scheduler.schedule(((
  7. new Runnable() {
  8. @Override
  9. public void run() {
  10. System.out.println(
  11. " 开始 threadId = "
  12. + Thread.currentThread().getId()
  13. + ",,,threadName = " + Thread.currentThread().getName()
  14. + ",,,时间" + formatDateToString(new Date())
  15. );
  16. try {
  17. Thread.sleep(1000);
  18. System.out.println(
  19. " 结束 threadId = "
  20. + Thread.currentThread().getId()
  21. + ",,,threadName = " + Thread.currentThread().getName()
  22. + ",,,时间" + formatDateToString(new Date())
  23. );
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. })),
  29. 5,
  30. TimeUnit.SECONDS);
  31. }
  32. public static String formatDateToString(Date time) {
  33. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  34. return sdf.format(time);
  35. }

 只运行一次

 

划重点一:

cheduleAtFixedRate ,是以上一个任务开始的时间计时,120秒过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行,之前我看到一些博客直接说的是按固定频率执行,但是不严谨,要注意如果你的执行任务的时间超过了间隔时间,那么就会变成一直连续循环执行,间隔时间的参数其实已经没发挥出作用了,其实一般我们也不会犯这样的错误,只是要清楚,固定频率是有条件的就行。

scheduleWithFixedDelay,是以上一个任务结束时开始计时,120秒过去后,立即执行。

划重点二:

关于定时线程池,好多人认为设置好频率(比如1Min),它会按照这个间隔按部就班的工作。但是,如果其中一次调度任务卡住的话,不仅这次调度失败,而且整个线程池也会停在这次调度上。 

源码中的注解也清楚的写着 If any execution of the task encounters an exception, subsequent executions are suppressed.

因此,注意在使用的时候涉及一些可能因为网络或者其他一些原因导致异常,要注意用try...catch...捕获

先写两个错误的示范和项目中注意的事项: 

错误一:cheduleAtFixedRate 这下面就是如果没有进行异常捕获,导致后续调度任务禁止 

  1. private static ScheduledExecutorService scheduler;
  2. public static void main(String[] args) throws Exception {
  3. scheduler = Executors.newScheduledThreadPool(5);
  4. // 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
  5. scheduler.scheduleAtFixedRate(((
  6. new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(
  10. " 开始 threadId = "
  11. + Thread.currentThread().getId()
  12. + ",,,threadName = " + Thread.currentThread().getName()
  13. + ",,,时间" + formatDateToString(new Date())
  14. );
  15. try {
  16. Thread.sleep(3000);
  17. System.out.println(
  18. " 结束 threadId = "
  19. + Thread.currentThread().getId()
  20. + ",,,threadName = " + Thread.currentThread().getName()
  21. + ",,,时间" + formatDateToString(new Date())
  22. );
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. //模拟抛出异常
  27. if (1 == 1) {
  28. throw new RuntimeException("异常");
  29. }
  30. }
  31. })),
  32. 0, 1,
  33. TimeUnit.SECONDS);
  34. Thread.sleep(20000);
  35. System.out.println(
  36. " 主线程 threadId = "
  37. + Thread.currentThread().getId()
  38. + ",,,threadName = " + Thread.currentThread().getName()
  39. + ",,,时间" + formatDateToString(new Date())
  40. );
  41. }
  42. public static String formatDateToString(Date time) {
  43. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  44. return sdf.format(time);
  45. }

错误二:scheduleAtFixedRate ,设置的period 间隔时间 小于了任务执行时间,导致任务连续执行

  1. private static ScheduledExecutorService scheduler;
  2. public static void main(String[] args) throws Exception {
  3. scheduler = Executors.newScheduledThreadPool(5);
  4. // 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
  5. scheduler.scheduleAtFixedRate(((
  6. new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(
  10. " 开始 threadId = "
  11. + Thread.currentThread().getId()
  12. + ",,,threadName = " + Thread.currentThread().getName()
  13. + ",,,时间" + formatDateToString(new Date())
  14. );
  15. try {
  16. Thread.sleep(3000);
  17. System.out.println(
  18. " 结束 threadId = "
  19. + Thread.currentThread().getId()
  20. + ",,,threadName = " + Thread.currentThread().getName()
  21. + ",,,时间" + formatDateToString(new Date())
  22. );
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. })),
  28. 0, 1,
  29. TimeUnit.SECONDS);
  30. }
  31. public static String formatDateToString(Date time) {
  32. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  33. return sdf.format(time);
  34. }

可以看到其实间隔时间已经变成了 任务执行时间来控制,但是一般来说,很少有任务执行时间超过间隔时间,但是这个知识点还是要知道。 

 

scheduleAtFixedRate 正常使用 延迟时间1S,执行任务时间 1S,间隔时间3S,
  1. private static ScheduledExecutorService scheduler;
  2. public static void main(String[] args) throws Exception {
  3. scheduler = Executors.newScheduledThreadPool(5);
  4. // 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
  5. scheduler.scheduleAtFixedRate(((
  6. new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(
  10. " 开始 threadId = "
  11. + Thread.currentThread().getId()
  12. + ",,,threadName = " + Thread.currentThread().getName()
  13. + ",,,时间" + formatDateToString(new Date())
  14. );
  15. try {
  16. Thread.sleep(1000);
  17. System.out.println(
  18. " 结束 threadId = "
  19. + Thread.currentThread().getId()
  20. + ",,,threadName = " + Thread.currentThread().getName()
  21. + ",,,时间" + formatDateToString(new Date())
  22. );
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. })),
  28. 0, 3,
  29. TimeUnit.SECONDS);
  30. }
  31. public static String formatDateToString(Date time) {
  32. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  33. return sdf.format(time);
  34. }

可以看到任务开始时间的间隔 为我们设置的 period 间隔时间3s,计算方式就是 initialDelay 初始延迟时间(第一次任务执行的间隔时间) +  n * period(间隔时间) 0s  3s  6s 来计算

scheduleWithFixedDelay 正常使用
  1. private static ScheduledExecutorService scheduler;
  2. public static void main(String[] args) throws Exception {
  3. scheduler = Executors.newScheduledThreadPool(5);
  4. // 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
  5. scheduler.scheduleWithFixedDelay(((
  6. new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(
  10. " 开始 threadId = "
  11. + Thread.currentThread().getId()
  12. + ",,,threadName = " + Thread.currentThread().getName()
  13. + ",,,时间" + formatDateToString(new Date())
  14. );
  15. try {
  16. Thread.sleep(3000);
  17. System.out.println(
  18. " 结束 threadId = "
  19. + Thread.currentThread().getId()
  20. + ",,,threadName = " + Thread.currentThread().getName()
  21. + ",,,时间" + formatDateToString(new Date())
  22. );
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. })),
  28. 0, 1,
  29. TimeUnit.SECONDS);
  30. }
  31. public static String formatDateToString(Date time) {
  32. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  33. return sdf.format(time);
  34. }
scheduleWithFixedDelay以上一次任务的结束时间 + 延迟时间 = 下一次任务的开始时间。 

 

以上就是两个循环任务的使用

 

配合ThreadFactory 使用

ThreadFactory 一般用来管理线程的创建或者其他一些操作管理,这里我们重点是说下ScheduledExecutorService,关于ThreadFactory的用法和分析就不深入说了。

  1. private static ScheduledExecutorService scheduler;
  2. public static void main(String[] args) throws Exception {
  3. scheduler = Executors.newScheduledThreadPool(5, new ThreadFactory() {
  4. private AtomicInteger counter = new AtomicInteger(0);
  5. //可以在这里对线程做一些操作
  6. @Override
  7. public Thread newThread(Runnable r) {
  8. int count = counter.incrementAndGet();
  9. System.out.println("线程创建counter = " + count);
  10. Thread thread = new Thread(r);
  11. thread.setName("测试线程"+count);
  12. return thread;
  13. }
  14. });
  15. System.out.println("main thread time : " + formatDateToString(new Date()));
  16. // 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
  17. scheduler.scheduleAtFixedRate(((
  18. new Runnable() {
  19. @Override
  20. public void run() {
  21. System.out.println(
  22. " 开始 threadId = "
  23. + Thread.currentThread().getId()
  24. + ",,,threadName = " + Thread.currentThread().getName()
  25. + ",,,时间" + formatDateToString(new Date())
  26. );
  27. }
  28. })),
  29. 1,5,
  30. TimeUnit.SECONDS);
  31. }
  32. public static String formatDateToString(Date time) {
  33. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  34. return sdf.format(time);
  35. }

 其实就是提供了一个扩展点,允许你在创建线程的时候,做一些管理或者说一些其他的操作

也可以使用 ThreadFactoryBuilder 构建类 来构建FactoryBuilder

  1. scheduler = Executors.newScheduledThreadPool(5,new ThreadFactoryBuilder()
  2. .setNameFormat("测试线程-%d")
  3. // .setDaemon(true) //这个参数是设置为守护线程 也叫 服务线程
  4. .build());

定义:守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
设置:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的setDaemon方法。用来设置线程

生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。

比如: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

当java虚拟机中没有非守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底 会出现什么问题。

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/83882
推荐阅读
相关标签
  

闽ICP备14008679号