简单的理解:
新来的任务首先判断当前线程数是否大于核心线程数,如果小于则创建新线程,大于则判断队列是否已满,没有满则进入队列,满了的话就判断当前线程数是否大于最大核心数,如果没有达到最大核心线程数,则去创建新线程。任务执行完成后,根据超时时间销毁超过核心线程数的部分。
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:
Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
补充:this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
Runnable
/Callable
)执行任务需要实现的 Runnable
接口 或 Callable
接口。Runnable
接口或 Callable
接口 实现类都可以被 ThreadPoolExecutor
或 ScheduledThreadPoolExecutor
执行。
Executor
)如下图所示,包括任务执行机制的核心接口 Executor
,以及继承自 Executor
接口的 ExecutorService
接口。ThreadPoolExecutor
和 ScheduledThreadPoolExecutor
这两个关键类实现了 ExecutorService 接口。
Future
)Future
接口以及 Future
接口的实现类 FutureTask
类都可以代表异步计算的结果。
当我们把 Runnable
接口 或 Callable
接口 的实现类提交给 ThreadPoolExecutor
或 ScheduledThreadPoolExecutor
执行。(调用 submit()
方法时会返回一个 FutureTask
对象)
Runnable
或者 Callable
接口的任务对象。Runnable
/Callable
接口的 对象直接交给 ExecutorService
执行: ExecutorService.execute(Runnable command)
)或者也可以把 Runnable
对象或Callable
对象提交给 ExecutorService
执行(ExecutorService.submit(Runnable task)
或 ExecutorService.submit(Callable task)
)。ExecutorService.submit(…)
,ExecutorService
将返回一个实现Future
接口的对象(我们刚刚也提到过了执行 execute()
方法和 submit()
方法的区别,submit()
会返回一个 FutureTask 对象)。由于 FutureTask
实现了 Runnable
,我们也可以创建 FutureTask
,然后直接交给 ExecutorService
执行。FutureTask.get()
方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)
来取消此任务的执行。几种常见的线程池
FixedThreadPool
被称为可重用固定线程数的线程池。
**FixedThreadPool
使用无界队列 LinkedBlockingQueue
(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列。可能会导致 OOM,
SingleThreadExecutor
是只有一个线程的线程池。
SingleThreadExecutor
使用无界队列 LinkedBlockingQueue
作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。可能会导致 OOM,
CachedThreadPool
是一个会根据需要创建新线程的线程池。
CachedThreadPool
的corePoolSize
被设置为空(0),maximumPoolSize
被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool
中线程处理任务的速度时,CachedThreadPool
会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。
以及上面提到的ScheduledThreadPoolExecutor
主要用来在给定的延迟后运行任务,或者定期执行任务。**
execute()
vs submit()
区别execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;submit()
方法用于提交需要返回值的任务。线程池会返回一个 Future
类型的对象,通过这个 Future
对象可以判断任务是否执行成功,并且可以通过 Future
的 get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。shutdown()
VSshutdownNow()
shutdown()
:关闭线程池,线程池的状态变为 SHUTDOWN
。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow()
:关闭线程池,线程的状态变为 STOP
。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。isTerminated()
VS isShutdown()
isShutDown
当调用 shutdown()
方法后返回为 true。isTerminated
当调用 shutdown()
方法后,并且所有提交的任务完成后返回为 true。上下文切换:
当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。任务从保存到再加载的过程就是一次上下文切换。
有一个简单并且适用面比较广的公式:
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;
CallerRunsPolicy:由调用execute方法的线程执行该任务;
DiscardPolicy:丢弃任务,但是不抛出异常;
DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。