Java 线程池的基本使用
一、为什么使用线程池
在Android开发中,经常需要用到多线程异步操作,如果使用new Thread().start,会造成一些问题:
- 每次new Thread,新建对象性能差。
- 在任务众多的情况下,系统要为每一个任务创建一个线程,不销毁的话过多线程会造成OOM。
- 如果任务执行完毕后销毁每一个线程,又会造成线程频繁地创建与销毁,这会频繁地调用GC机制,这会使性能降低,又非常耗时。
- 缺乏更多功能,如定时执行、定期执行、线程中断。
使用线程池可以带来的好处:
- 对多个线程进行统一地管理,避免资源竞争中出现的问题。
- 对线程进行复用,线程在执行完某个任务之后,可以执行另一个任务。
二、几种常见的线程池
1. ThreadPoolExecutor 最基本线程池
后面几种线程池可以视为这个基本线程池的扩展和详细使用
创建方式如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心线程数,默认情况下,即使核心线程处于闲置状态,它们也会一直存活。其有一个allowCoreThreadTimeOut属性,如果设置为true,那么核心线程池会有超时策略。超时的时长为第三个参数 keepAliveTime。如果超时,核心线程会被终结。
maximumPoolSize
最大线程数,当线程数量达到这个最大值时,后续添加的新任务会被阻塞。
keepAliveTime
非核心线程闲置的超时时长,超过这个时长,非核心线程会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime对核心线程同样有效。
unit
用于指定keepTimeAlive的时间单位,如TimeUnit.MILLISECONDS、TimeUnit.SECONDS等。
workQueue
线程中的任务队列,通过线程池的execute方法提交的Runnable对象会储存在这个队列中。
threadFactory(不常用)
线程工厂,提供创建新线程的功能。ThreadFactory是一个借口,只有一个方法:Thread newThread(Runnable r),可以用于设置线程名字。
handler(不常用)
当线程池无法执行新任务时,可能是因为队列已满,或者执行失败,这时会调用handler的rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法来抛出异常。
执行规则:
- 如果线程未达到核心线程数量,那么直接启动一个核心线程。
- 如果线程达到核心线程的数量,任务会被插入到任务队列(workQueue)排队。
- 如果任务队列已满导致步骤2无法插入到任务队列,那么开启一个非核心线程执行。
- 如果步骤3的线程数量达到线程池规定数目(maxmumPoolSize),那么拒绝执行此任务。
demo
val threadPoolExecutor = ThreadPoolExecutor(3, 5, 1, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>(100))
base_button.setOnClickListener {
var i = 0
while (i < 30) {
val final = i
val runnable = Runnable {
Thread.sleep(2000)
Log.d("Thread", "run:$final")
}
threadPoolExecutor.execute(runnable)
i++
}
}
结果会每隔2s,打印3个日志,日志内容为连续数
小问题:日志会按顺序打印吗?每次循环都有sleep,为什么可以做到每隔2s打印3个日志?
2、FixedThreadPool (可重用固定线程数)
参数为核心线程数,只有核心线程,无非核心线程,无超时机制,阻塞队列无界。
当线程处于空闲状态时,只要线程池不被关闭它们就并不会被回收。当所有线程都处于活动状态,新任务就会处于等待状态,直到有线程空闲出来。适用于执行长期任务。
demo
val fixedThreadPoolExecutor = Executors.newFixedThreadPool(5)
var i = 0
while (i < 30) {
val final = i
val runnable = Runnable {
Thread.sleep(2000)
Log.d("Thread", "run:$final")
}
fixedThreadPoolExecutor.execute(runnable)
i++
}
结果为每2s打印5次日志
3、CachedThreadPool (按需创建)
线程数量不定,没有核心线程,只有非核心线程,每个线程空闲等待的时间为60s,采用SynchronousQueue队列。当线程都处于活动状态时,线程池会创建新线程来执行任务,否则就会复用空闲的线程。SynchronousQueue可以理解成一个无法储存元素的队列,因为其中的任务会被马上执行。这种线程池适用于大量耗时少的任务。当所有线程都处于闲置状态,线程会逐渐因为超时被停止,线程池中就没有线程,几乎不占系统资源。
demo
val cachedThreadPoolExecutor = Executors.newCachedThreadPool()
var i = 0
while (i < 30) {
val final = i
val runnable = Runnable {
Thread.sleep(2000)
Log.d("Thread", "run:$final")
}
cachedThreadPoolExecutor.execute(runnable)
i++
}
结果为2s之后打印30次日志
4、ScheduledThreadPool(定时延时执行)
核心线程数固定,非核心线程数没有限制,非核心线程闲置时会被立即回收。主要用于执行定时任务和周期性重复任务。
demo
val scheduledThreadPoolExecutor = Executors.newScheduledThreadPool(3)
val runnable = Runnable {
Log.d("Thread", "This task is delayed to execute")
}
scheduled_button1.setOnClickListener {
// 延迟3s后启动任务,只执行一次
scheduledThreadPoolExecutor.schedule(runnable, 3, TimeUnit.SECONDS)
}
scheduled_button2.setOnClickListener {
// 延迟3s后启动,每隔1s执行一次
scheduledThreadPoolExecutor.scheduleAtFixedRate(runnable, 3, 1, TimeUnit.SECONDS)
}
scheduled_button3.setOnClickListener {
// 第一次延迟3s后执行,之后每隔1s执行一次
scheduledThreadPoolExecutor.scheduleWithFixedDelay(runnable, 3, 1 ,TimeUnit.SECONDS)
}
运行结果如注释所示
5、SingleThreadExecutor(单核fixed)
只有一个核心线程,存活时间无限
demo
val singleThreadPoolExecutor = Executors.newSingleThreadExecutor()
var i = 0
while (i < 30) {
val final = i
val runnable = Runnable {
Thread.sleep(2000)
Log.d("Thread", "run:$final")
}
singleThreadPoolExecutor.execute(runnable)
i++
}
结果为每隔2s打印一次日志,因为只有一个核心线程,当该线程被占用时,其他任务需要等待。
三、线程池的其他方法
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。
5.beforeExecute() 任务执行前执行的方法
6.afterExecute() 任务执行结束后执行的方法
7.terminated() 线程池关闭后执行的方法