为什么要使用线程池
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,使用线程池可以进行统一分配、调优和监控。
线程池的实现原理
- 如果当前运行的线程少于
corePoolSize
,则创建新线程来执行任务(这一步需获取全局锁)。 - 如果运行的线程等于或多于
corePoolSize
,则将任务加入BlockingQueue
。 - 如果队列已满,则创建新的线程来处理任务(这一步需获取全局锁)。
- 如果创建新线程将使当前运行的线程超出
maximumPoolSize
,任务将被拒绝,并使用相关饱和策略进行处理,默认是直接抛出异常。
之所以采用这个步骤,是因为在执行execute()
方法时,尽可能地避免获取全局锁。在线程池完成预热之后(当前运行的线程数大于等于corePoolSize
),几乎所有的execute()
方法调用都是执行步骤2,而步骤2不需要获取全局锁。
Executor框架
Executor框架内部使用了线程池机制,它在java.util.cocurrent
包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,通过Executor
来启动线程比使用Thread
的start()
方法更易管理且效率更好。
Executor框架的结构与成员
ThreadPoolExecutor
ThreadPoolExecutor
是Executor框架的最核心的类,是线程池的实现类,用来执行被提交的任务,通常使用工厂类Executors
来创建,有以下三种类型:
FixedThreadPool
:线程数固定。适用于为了满足资源管理而需要限制线程数的场景,适用于负载比较重的服务器。SingleThreadExecutor
:只有一个线程。适用于需要保证顺序地执行各个任务,并且在任意时间点不会有多个线程是活动的场景。CachedThreadPool
:根据需要创建新线程。空闲线程等待新任务超过60秒就会被终止。适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。
FixedThreadPool
和SingleThreadExecutor
使用无界队列LinkedBlockingQueue
作为线程池的工作队列。CachedThreadPool
使用没有容量的SynchronousQueue
作为线程池的工作队列。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
继承自ThreadPoolExecutor
,主要用来在给定的延迟之后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor
的功能与Timer
类似,但ScheduledThreadPoolExecutor
功能更强大、更灵活,Timer
对应的是单个后台线程,而ScheduledThreadPoolExecutor
可以在构造函数中指定多个对应的后台线程数。
FutureTask
Future
接口和实现Future
接口的FutureTask
类,代表异步计算的结果。FutureTask
除了实现Future
接口外,还实现了Runnable
接口。可以把FutureTask
交给Executor
执行,也可以通过submit()
方法返回一个FutureTask
,然后执行FutureTask.get()
方法。
线程池的使用
线程池的创建
我们可以通过ThreadPoolExecutor
来创建一个线程池:
1 | new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler); |
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其它空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。
向线程池提交任务
可以使用execute()
方法提交任务,但是execute()
方法没有返回值,所以无法判断任务是否被线程池执行成功。也可以使用submit()
方法来提交任务,它会返回一个future
,可以通过这个future
的get()
方法来获取返回值,get()
方法会阻塞直到任务完成。
关闭线程池
可以通过调用线程池的shutdown()
或shutdownNow()
方法来关闭线程池。
shutdown()
先将线程池状态置为SHUTDOWN
,停止接受外部提交的新任务,而等到正在执行的任务以及队列中等待的任务执行完才真正停止。shutdownNow()
先将线程池状态置为STOP
,停止接受外部提交的新任务,忽略队列里等待的任务,使用interrupt()
方法尝试将正在跑的任务中断,然后返回未执行的任务列表。
注意,如果线程中没有sleep
、wait
、Condition
等应用,interrupt()
方法是无法中断当前的线程的。所以,shutdownNow()
并不代表线程池一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。