最近在看OkHttp的源码,看着看着就看到了有线程池的地方,以前自己对这个东西就也就感到云里雾里的,所以
想把线程池的知识点和源码完整的看一篇
1.线程池有什么用?
a.重用线程池中存在的线程,避免因为线程的大量创建和销毁所带来的性能开销 ,完成一项任务的时间T=创建线
程的时间+在线程中执行任务的时间+线程销毁的时间, 而线程池的出现可以大大减少创建线程的时间和线程销毁的
时间,从而提高app的性能
b.能有限的控制线程池的最大并发数,并且大量的线程互相抢占资源而导致的阻塞现象
c.对线程池中线程进行有效的跟进,当我们不需要处理的时候可以将它shutdow掉
并提供定时执行以及制定间隔循环执行,
2.ThreadPoolExcutor类详解
threadPoolExcutor是线程池中最核心的类,所以要想弄懂线程池一定要弄懂这个类
(1)首先来了解一下构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
接下来看看具体每个参数是什么意思?
corePoolSize:核心线程池大小
maximumPoolSize:最大线程池大小
keepAliveTime:当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,
如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
unit:keepAliveTime时间单位
workQueue:阻塞任务队列
threadFactory:新建线程工厂
handler: 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来做拒绝处理
(2)BlockingQueue详解:用于保存等待执行的线程任务的阻塞队列
ArrayBlockingQueue(少):是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue 静态工厂方法Executors.newFixedThreadPool()使用了这个队列,如果不执行队列的大小,那么该
队列就是一个无界队列,无界队列的特征见PriorityBlockingQueue
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程
PriorityBlockingQueue(少):一个具有优先级的无限阻塞队列,使用无界队列将导致将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize和keepAliveTime的值也就无效了)
(3)ThreadFactory作用:
线程工厂,主要用来根据提交的新任务创建线程对象
static class PicassoThreadFactory implements ThreadFactory {
@SuppressWarnings("NullableProblems")
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
(4)ThreadPoolExcutor的执行步骤
①、当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者
“我不能再接受任务了”。
②、当一个线程完成任务时,它会从队列中取下一个任务来执行。
③、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于
corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
这个过程说明,并不是先加入任务就一定会先执行。假设队列大小为 4,corePoolSize为2,maximumPoolSize为6,
那么当加入15个任务时,执行的顺序类似这样:首先执行任务 1、2,然后任务3~6被放入队列。这时候队列满了,
任务7、8、9、10 会被马上执行,而任务 11~15 则会抛出异常。最终顺序是:1、2、7、8、9、10、3、4、5、6。
当然这个过程是针对指定大小的ArrayBlockingQueue
中
3.ThreadPoolExcutor的使用
(1)newFixedThreadPool(2)
//指定了corePoolSize与maximumPoolSize为2
ExecutorServicer pool=Executors.newFixedThreadPool(2);
// 创建线程
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
// 将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
// 关闭线程池
pool.shutdown();
private class MtThread extends Thread{...}
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
// 创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
// 将线程放入池中进行执行
pool.execute(t1);
// 使用延迟执行风格的方法
pool.schedule(t2, 1000, TimeUnit.MILLISECONDS);
pool.schedule(t3, 10, TimeUnit.MILLISECONDS);
// 关闭线程池
pool.shutdown();
来自定义线程池
(5)ThreadPoolExecutor中几个重要的方法
① execute(Runnable r):向线程池添加一个线程任务
② Future> submit(Runnable r):向线程池添加一个线程任务,可以返回任务执行结果
③ shutdown():关闭线程池,不接收新的任务,关闭后,正在等待执行的任务不受任何影响,会正常执行,无返回值!
④ shutdownNow():关闭线程池,也不接收新的Task,并停止正等待执行的Task(也就是说,
执行到一半的任务将正常执行下去),最终还会给你返回一个正在等待执行但线程池关闭却没有被执行的Task集合
我们执行Runnable任务,他的run()方法是没有返回值的,那如果我们想要执行完一个任务,并且能够拿到一个返回值结果,那么应该怎么做呢?这个在我的另一篇博客已经将的很清楚了就不重复讲了Future和Callable详解