为什么要使用线程池

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,使用线程池可以进行统一分配、调优和监控。

线程池的实现原理

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(这一步需获取全局锁)。
  2. 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
  3. 如果队列已满,则创建新的线程来处理任务(这一步需获取全局锁)。
  4. 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并使用相关饱和策略进行处理,默认是直接抛出异常。

之所以采用这个步骤,是因为在执行execute()方法时,尽可能地避免获取全局锁。在线程池完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

Executor框架

Executor框架内部使用了线程池机制,它在java.util.cocurrent包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,通过Executor来启动线程比使用Threadstart()方法更易管理且效率更好。

Executor框架的结构与成员

ThreadPoolExecutor

ThreadPoolExecutor是Executor框架的最核心的类,是线程池的实现类,用来执行被提交的任务,通常使用工厂类Executors来创建,有以下三种类型:

  • FixedThreadPool:线程数固定。适用于为了满足资源管理而需要限制线程数的场景,适用于负载比较重的服务器。
  • SingleThreadExecutor:只有一个线程。适用于需要保证顺序地执行各个任务,并且在任意时间点不会有多个线程是活动的场景。
  • CachedThreadPool:根据需要创建新线程。空闲线程等待新任务超过60秒就会被终止。适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。

FixedThreadPoolSingleThreadExecutor使用无界队列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,可以通过这个futureget()方法来获取返回值,get()方法会阻塞直到任务完成。

关闭线程池

可以通过调用线程池的shutdown()shutdownNow()方法来关闭线程池。

  • shutdown()先将线程池状态置为SHUTDOWN,停止接受外部提交的新任务,而等到正在执行的任务以及队列中等待的任务执行完才真正停止。
  • shutdownNow()先将线程池状态置为STOP,停止接受外部提交的新任务,忽略队列里等待的任务,使用interrupt()方法尝试将正在跑的任务中断,然后返回未执行的任务列表。

注意,如果线程中没有sleepwaitCondition等应用,interrupt()方法是无法中断当前的线程的。所以,shutdownNow()并不代表线程池一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。

参考资料