You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
android-notes/blogs/Java/口水话/线程池.md

4.9 KiB

线程池口水话

目录

  1. 线程池存在的意义
  2. 源码哔哔
    1. 构造函数参数的意思
    2. 线程池五种状态的切换
    3. execute 方法

线程池存在的意义

线程池主要解决了两个问题:

  1. 当执行大量异步任务时线程池能够提供较好的性能,避免线程的频繁创建和销毁。
  2. 提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等。

这种缓存对象实例的池化思想在 Android 中也有应用,比如 Handler 中使用到的 Message 消息池。

在 Java 中的线程池是用 ThreadPoolExecutor 类来实现的,它的构造方法中有一系列参数需要指定。首先是 corePoolSize 核心线程数量,当有任务通过 execute 方法提交时,如果当前运行的线程数 workCount 小于核心线程数,就新建一个线程去执行任务,如果 workCount 大于等于核心线程数并且队列未满时就扔进队列里面,等待有空闲的线程自己去从队列里面去取然后执行;如果此时队列满了,但是未达到最大线程数,就新建一个线程去执行任务,如果已经达到了最大线程数并且队列满了,就执行拒绝策略。第二个参数就是指定最大线程数,第三个参数是阻塞队列,在任务提交时,如果线程池中的线程数量大于等于 corePoolSize 时,就会把该任务封装成一个 Worker 对象放入等待队列里。在使用 Executors 的 newFixedThreadPool 或 newSingleThreadPool 创建线程池时使用的是 LinkedBlockingQueue,而在使用 newCachedThreadPool 使用的是 SynchronousQueue。但是通常不建议直接使用 Executors 创建线程池,因为 LinkedBlockingQueue 默认大小是 Integer.MAX_VALUE,可能会撑爆内存。第四个参数 keepAliveTime 是非核心线程的存活时间,第五个参数 threadFactory 是用来创建新线程的,默认是 DefaultThreadFactory,在 newThread 里面会设置线程的 group、优先级、是否是守护线程以及线程名称。最后一个参数是拒绝策略,默认是 AbortPolicy 直接抛异常。其次还有其他三个,这四个都定义在了 ThreadPoolExecutor 的内部类中,CallerRunsPolicy(使用调用者所在线程来运行任务)、DiscardOldestPolicy(低调用 poll 丢弃一个任务,执行当前任务)和 DiscardPolicy(默认丢弃,不抛异常),ThreadPoolExecutor 一共有五个内部类,还有一个就是上面讲到的 Worker。

然后再说说 ThreadPoolExecutor 的一个比较重要的字段,即原子类 ctl,它的高三位表示 runState 运行状态,后二十九位表示 workCount。线程池一共有五种运行状态,RUNNING 状态就是能够接受新提交的任务,并且也能处理阻塞队列中的任务;SHUTDOWN 关闭状态,就是不在接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务,当调用 shutdown 方法会使线程池进入到该状态;STOP 状态不能接受新任务,也不处理队列中的任务,会尝试中断正在处理任务的线程,当调用 shutdownNow 时进行该状态;TIDYING 状态是所有任务已经终止了,workCount 为零。线程池进入该状态后就会调用 terminated 方法进入 TERMINATED 状态。

最后就是最关键的用来提交任务 execute 方法,如果 workerCount 小于 corePoolSize,就会调用 addWorker 在线程池中创建一个新的线程并执行。线程池中的每一个线程都被封装成了一个 Worker 对象,ThreadPool 维护的其实就是一组 Worker 对象,Worker 对象有一个成员变量 thread,执行任务时就是调用 thread.start 执行它自己的 run 方法,Worker 本身就是一个 Runnable。调用 runWorker 时会在一个 while 循环中判断,如果它自己的 runnable 不为空或者从队列里面取得任务不为空就会一直执行。也就是说,在 Worker 执行完当前任务之后,会再去从队列里面取任务执行,也就是调用 getTask,它的实现就是从阻塞队列里面取任务,调用 poll 或者 take。在所有任务执行完成之后,就会判断是否回收线程。

需要注意的是,线程池使用 FutureTask 时如果把拒绝策略设置为 DiscardPolicy 和 DiscardOldestPolicy,并且在被拒绝的任务的 Future 对象上调用了无参 get 方法,那么调用线程会一直被阻塞。所以当使用 Future 时,尽量使用带超时时间的 get 方法,这样即使使用了 DiscardPolicy 拒绝策略也不至于一直等待,超时时间到了就会自动返回。如果非要使用不带参数的 get 方法那就只能重写 DiscardPolicy 的拒绝策略了,发现是 FutureTask 就调用 cancel,并且需要在 get 方法加 try-catch。

或者可以参考 Android 的 AsyncTask 的做法,当发生拒绝策略时,复写的拒绝策略其实是再创建一个核心线程池可回收的线程池来处理任务。

至此,线程池就讲完了。