Java基础回顾系列(3)_线程池

137 阅读3分钟

这部分内容是跟着前一篇的锁的,无奈超字数了,只能拆分成两篇了~

线程池

线程池的核心就是:队列 + 线程复用机制

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

1:通过减少任务间的调度开销 (主要是通过线程池中的线程被重复使用的方式),来提高大量任务时的执行性能;

2:提供了一种方式来管理线程和消费,维护基本数据统计等工作,比如统计已完成的任务数;

线程池跟线程一样,也是有状态的。

线程池的状态扭转:

Q :线程池是如何实现任务调度的?

任务调度流程:

Q :线程池如何实现复用?

线程池复用的实现在于理解Worker这个类设计。 Worker本身实现了Runable接口,并且,在初始化时就启动了一条线程用执行任务:

Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

好了,思考,如果是你,你会如何设计?我们知道,线程池还有一个阻塞队列,所以,是否可以这样实现:不断地从阻塞队列中取出任务来执行,这样不就实现了复用线程了吗??

实际上,线程池也是这样实现的: runWorker方法就是执行任务,采用while循环不断从阻塞队列中去除任务来执行。

逻辑如图:

这里,又引出另外一个问题了,如果是不断轮询,那么如何回收非核心线程的呢?

这里可以发散一下思维的,我们知道,很多第三方库都有复用的概念的,举个例子,OkHttp的TCP连接池,复用的是TCP连接;还有我们Glide,它复用的是Bitmap的内存空间。OkHttp是通过Deque + cleanRunnable来管理复用与清理的,而Glide则是通过LruCache来实现的。

Q :如何实现超过60s就被回收?

Worker被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。

try {
  while (task != null || (task = getTask()) != null) {
    //执行任务
  }
} finally {
  processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}

getTask()方法实现:

private Runnable getTask() {
  ...
   Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
}

线程回收的工作是在processWorkerExit方法完成的,如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程

Q: 线程池的种类?

Q:线程池的拒绝策略

RejectedExecutionHandler提供了四种方式来处理任务拒绝策略:

  • 直接丢弃(DiscardPolicy)
  • 丢弃队列中最老的任务(DiscardOldestPolicy)。
  • 抛异常(AbortPolicy)
  • 将任务分给调用线程来执行(CallerRunsPolicy)。