前言#
android的线程池主要有四个:
newSingleThreadExecutor:单线程池,同时只有一个线程在跑。
newCachedThreadPool() :回收型线程池,可以重复利用之前创建过的线程,运行线程最大数是Integer.MAX_VALUE。
newFixedThreadPool() :固定大小的线程池,跟回收型线程池类似,只是可以限制同时运行的线程数量
哎???第四个是什么来着,不知道你有没有想起来,反正我是没记住,结果面试的时候就栽了个跟头。
正文#
第四种线程池是ScheduledExecutorService,我平时没有用过,他的最大优点除了线程池的特性以外,可以实现循环或延迟任务。
<h2>ScheduledExecutorService 和 Timer 的区别</h2>
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的用法,最后两者结合使用,分析并解决可能出现的问题。
由于线程池的源码还是很多的,这里就不具体的解释了,想具体了解的朋友可以自己去查看源码或者网上查看资料。
最后带上源码链接,有问题或者建议可以留言。