理解线程与线程池的原理和使用
Java中实现线程的2种方式
在Android中,为了给用户一种良好的体验,谷歌推荐开发人员将耗时操作放在子线程中操作,因此在目前的各类app中,线程在Android开发中的应用十分的普遍,比如说网络请求、访问数据库、操作文件等等。
在Java中,已经提供了2种实现线程的方式。
- 继承Thread类
代码示例:
class MyThread extends Thread{
@Override
public void run() {
//耗时操作
}
}
new MyThread().start();
- 实现Runnable接口
代码示例:
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
}
}).start();
两种方式虽然都可以实现,但是两者还是有一定的区别,因为Java的单继承性质,继承了Thread类则不能在继承其他的类,而实现Runnable接口则没有这种限制。另外在多线程去操作同一个资源的时候,也应该选择使用实现Runnable接口的方法来实现。
那么,我们思考一个问题,首先我们知道,开起线程是要占用系统资源的,当遇到这样一种情况,比如我们需要同时下载100张图片的时候,同时开起这么多的线程,需要的资源开销是非常可怕的,那么我们有没有一种方式,可以一次只开起指定个数的线程,当这些任务完成后,在去完成新的任务呢?答案是肯定的,我们可以通过ThreadPoolExecutor类来实现这个流程。
引入线程池
引入线程池的目的
针对多线程并发的问题,比如上一部分提到的业务场景,引入线程池的好处就比较明显了:
- 可以控制同一时间内线程的最大并发数,合理利用系统资源,提高应用性能。
- 当任务完成后,可以重用已经创建的的线程,避免频繁创建新的线程而导致的GC。
- 通过Executors工具类,可以更方便的使用线程池,控制线程的最大并发数、线程的定时任务、单线程的顺序执行等。
认识ThreadPoolExecutor
ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService又实现了ExecutorService接口,ExecutorService接口最终继承自Executor接口。
首先我们看一下ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
那么这些参数都代表什么意思呢?
类型 | 参数 | 意义 |
---|---|---|
int | corePoolSize | 线程池中的核心线程数量 |
int | maximumPoolSize | 线程池中的最大线程数量 |
long | keepAliveTime | 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。 |
TimeUnit | unit | keepAliveTime的单位,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)等 |
BlockingQueue | workQueue | 任务队列,主要用来存储已经提交但未被执行的任务 |
ThreadFactory | threadFactory | 线程工厂,用来创建线程池中的线程,通常用默认的即可 |
RejectedExecutionHandler | handler | 通常叫做拒绝策略,在线程池已经关闭的情况下,或者任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务 。在上面两种情况下,只要满足其中一种时,在使用execute()来提交新的任务时将会拒绝,而默认的拒绝策略是抛一个Reject |