浅谈Java线程池是怎样运行的
caz 人气:0异步编程工具在Android开发中目前最被推荐的就是Kotlin协程,在引入Kotlin协程机制前,除了响应式扩展(RxJava)兼任异步编程工具外,Java API中线程与线程池就是最重要异步编程手段。而对于Android平台的Kotlin协程实现来说,依然使用的是线程池来作为任务执行的载体,所以可以将Android平台的Kotlin协程简单的理解是对线程池的一种高度封装。
Executors.newFixedThreadPool(10).asCoroutineDispatcher() Dispatchers.IO.asExecutor()
因此我们先了解Java线程池是如何运行的,再深入理解Kotlin协程是如何实现的。
从Thread到Executor
线程的创建通过Thread类,为了复用线程而进行池化就有了线程池。线程池带来了两点明显优势:
- 降低重复创建线程的开销
- 将任务与线程管理解耦
Executor接口就是第二点的体现。其execute方法用于执行任务,不必关系这个任务执行的载体究竟是什么,到底有没有创建线程。ThreadPoolExecutor实现类就是这个任务执行器的线程池实现。
ThreadPoolExecutor的任务添加与线程复用
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); }//1 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); }//2 else if (!addWorker(command, false)) reject(command);//3 }
查看execute方法可以清楚了解其运行方式:
- 当线程数小于corePoolSize时,创建线程并执行任务;
- 若任务未通过步骤1添加,则入队workQueue;(主要逻辑在if的条件判断中,而if内的逻辑处理的是在一些异常下,对入队的回滚或补充创建线程)
- 若任务未入队,则仍创建线程(上限为maximumPoolSize)并执行任务,失败则执行拒绝策略。
boolean addWorker(Runnable firstTask, boolean core)
就是创建线程的方法,方法中第二个参数代表以corePoolSize还是maximumPoolSize为界,方法内其余创建线程的细节逻辑不深究。但要关注一下线程的封装类Worker,addWorker方法内调用了Worker内被封装线程的start方法,执行Worker的run方法。我们将run方法内的runWorker简化如下:
void runWorker(Worker w) { Runnable task = w.firstTask; w.firstTask = null; while (task != null || (task = getTask()) != null) { task.run(); } }
可以发现,初始任务执行完后,不断通过getTask方法获取任务执行,以此来实现线程的复用,而不是只执行完一个任务就销毁了线程。
另外查看简化后的getTask方法如下:
private Runnable getTask() { boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; } catch (InterruptedException retry) { } }
任务是从阻塞队列workQueue中取出的,并且根据配置allowCoreThreadTimeOut与线程个数是否大于corePoolSize,来决定使用BlockingQueue<Runable>的带超时时间的取任务方法poll,还是阻塞取任务方法take,以实现任务列表为空时适时销毁线程还是阻塞线程。
回过头来看ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
我们可以清楚的明白每个参数的含义,以及它是如何影响线程池中线程的复用了。
加载全部内容