使用线程来进行异步任务,这对于大多数人而言还是轻车熟路的,而且使用起来比较简单,只需要通过new Thread() 之后 start 即可,当任务完成之后则会销毁,但是这里有一个弊端,就是如果一个高度任务,比如说定时处理异步事件,如果你每次执行都new Thread的话,这将导致线程频繁的创建与销毁,这样会导致占用大量的资源,这已经就十分的不友好了,而且所有创建的子线程都没有一个统一的管理,如果要优化,也应该从这里下手了,所以,我们,我们就更应该来了解一下线程池的使用了,使用线程池可以复用创建的子线程,不会反复创建和销毁,并且使用起来也不算太难。
ThreadPoolExecutor
我们要理解ThreadPoolExecutor,因为Android中其他几个线程池都是他延伸出来的,相当于他就是个爸爸,所以你要先了解ThreadPoolExecutor的前世今生,首先来看下他的构造函数的几个参数:
int corePoolSize
核心线程数量
int maximumPoolSize
最大线程数量
long keepAliveTime,
超时时长
TimeUnit unit,
时间枚举
BlockingQueue
任务队列
ThreadFactory threadFactory
线程工厂接口
RejectedExecutionHandler handler
拒绝执行的Handler
这里要注意,正常情况下,corePoolSize是一直存在的,并且当corePoolSize >= maximumPoolSize的时候,线程会被阻塞等待任务完成,keepAliveTime触发的时候非核心线程都会被回收掉。
ThreadPoolExecutor还是有很多可讲的,但是我选择的还是着重讲解Android中的四个线程池,网上对ThreadPoolExecutor的例子太多了,也比较杂,大家可以去看看。
Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,其中所实现的四个线程池每一个的作用都是不一样的,我们一起来看下:
FixThreadPool
FixThreadPool通过如下代码实现:
ExecutorService service = Executors.newFixedThreadPool(5);
Runnable r = new Runnable() {
@Override
public void run() {
Log.i(TAG, "newFixedThreadPool");
}
};
service.execute(r);
队列线程池,因为核心线程池是固定的,所以不管你如何execute,都会一个个来执行完成,因为直接创建使用,没有回收,所以他的优势是响应速度很快,效率更高。
这里我们可以模拟一下:
ExecutorService service = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
final int finalI = i;
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.i(TAG, "newFixedThreadPool:" + finalI );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
这里指定核心线程数为2,然后执行10个任务,得到的结果:
可以看出,首先他是无序的,其次他每隔两秒钟打印两个,也就验证了刚才的说法,他线程数满了之后需要等待,就是排队打饭一样。
SingleThreadPool
顾名思义,这个都不需要指定核心线程数,代码如下:
ExecutorService service = Executors.newSingleThreadExecutor();
Runnable r = new Runnable() {
@Override
public void run() {
Log.i(TAG, "newSingleThreadExecutor");
}
};
service.execute(r);
这个其实和newFixedThreadPool(1)是一样的,SingleThreadPool主要还是为了线程同步而来的,newFixedThreadPool可以同时执行多任务,所以肯定不是同步的,如果什么场景需要线程同步,那SingleThreadPool再合适不过了。
如果我们把上面的例子改成SingleThreadPool:
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int finalI = i;
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.i(TAG, "newSingleThreadExecutor:" + finalI );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
可以得到的结论:首先他是有序的,其次他每隔两秒则打印一次,而Log的输出也刚好验证了我的结论:
CachedThreadPool
CachedThreadPool还是比较多使用的,他不需要指定线程数,因为他的线程数很大,但是不存在核心线程,这也就意味着经常被回收,他的回收思路是,执行任务,任务结束后,保留60s,在60s来了新任务则继续使用刚才的线程,如果60s内无任务则回收线程,有点类似吃饭,你去盛饭没关系,但是你吃完走了我就要收盘子,间隙就在60s
ExecutorService service = Executors.newCachedThreadPool();
Runnable r = new Runnable() {
@Override
public void run() {
Log.i(TAG, "newCachedThreadPool");
}
};
service.execute(r);
我们继续改造我们的示例代码:
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int finalI = i;
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.i(TAG, "newCachedThreadPool:" + finalI );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
这段代码在前面两个线程池都使用到了,就是用来告诉你他们的区别,我们使用newCachedThreadPool执行后得到的结果:
看Log,我们得到的结论是:首先他也是无序的,并且他因为子线程够多,所以sleep两秒后直接全部打印出来了。
ScheduledThreadPool
这是一个可操作性较强的线程池,也是唯一一个可循环的线程池,便于一些重复性的工作使用
ScheduledExecutorService service = Executors. newScheduledThreadPool (5);
Runnable r = new Runnable() {
@Override
public void run() {
Log.i(TAG, "newScheduledThreadPool");
}
};
Log.i(TAG,"start");
service.scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);
scheduleAtFixedRate中有四个参数,第一个是任务,第二个是延时时长,第三个是循环时长,第四个是单位,也就是说,当启动scheduleAtFixedRate之后,在1s后才开始执行这个间隔2s的任务不断循环,可以这样理解,启动任务后,这里延时1s,然后才开始执行任务,以后每隔2s重复执行
我们拿着示例继续改造:
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 10; i++) {
final int finalI = i;
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.i(TAG, "newCachedThreadPool:" + finalI );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},1000,2000,TimeUnit.MILLISECONDS);
}
这段代码比上面的复杂一些,我们看打印:
这里得到的是什么结论呢?比较多,首先,他是无序的,其次他间隔2s无限重复,并且启动这个县城需要3s,也就是延迟的1s + sleep的3s。
到这里我相信大家还是比较清晰的认知线程池,但是线程池是结合实战的,不然的话也都是纸上谈兵,但是一般线程池也只是替换new Thread的操作,所以示例的话,大家可以自行寻找一下,后续的实战项目文章,我再带领大家学习线程池。
大家有兴趣可以加入我的星球,任何问题都能得到解答,还有专门的视频辅佐,快速进阶。