当前位置:   article > 正文

ScheduledExecutorService 使用及注意事项_scheduledexecutorservice.schedule

scheduledexecutorservice.schedule

还在手写循环线程?就用他(ScheduledExecutorService),优雅点!
优点:
最大优点除了线程池的特性以外,可以实现循环或延迟任务。

下面部分内容引用该博客: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);


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

在代码中已经有注释说明这三个方法的用法,重点是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", "嘿嘿嘿");
                }
            };
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

例如上面的代码,我在参数Runnable对象的run方法后,增加了一句log打印。

然后修改MainActivity的调用:

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

这里面我传入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");
                }
            };
        }
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我在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的用法,最后两者结合使用,分析并解决可能出现的问题。

由于线程池的源码还是很多的,这里就不具体的解释了,想具体了解的朋友可以自己去查看源码或者网上查看资料。

最后带上源码链接,有问题或者建议可以留言。

引用博客内容–结束
static {
        ScheduledExecutorService pruneTimer = new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "prune-cache-timer-"));
        pruneTimer.scheduleWithFixedDelay(() -> {
            LOG.warn("prune cache now----- ");  
        }, 1, 1, TimeUnit.HOURS);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

scheduleAtFixedRate 与 scheduleWithFixedDelay 的区别:

scheduleAtFixedRate ,是以上一个任务开始的时间计时,N秒过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。

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

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

闽ICP备14008679号