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);


    }
}
  • 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
  • 47

在代码中已经有注释说明这三个方法的用法,重点是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的用法,最后两者结合使用,分析并解决可能出现的问题。

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

原文出处:https://blog.csdn.net/u011315960/article/details/71422386

猜你喜欢

转载自blog.csdn.net/qq_35820350/article/details/81533378