池化技术是一种广泛应用的技术。线程池、数据库连接池、Http连接池都是这种技术的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
线程池提供了一种限制和管理资源(包括执行一个任务)的功能。同时每个线程池还维护一些基本统计信息,例如已完成的任务数量。
使用线程池可以带来以下好处:
- 降低资源消耗。
通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 - 提高响应速度。
当任务到达时,任务可以不需要等到线程创建就能立即执行。 - 提高线程的可管理性。
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Runnable接口和Callable接口的区别
Runnable
自Java 1.0以来一直存在,但Callable
仅在Java 1.5中引入,目的就是为了来处理Runnable
不支持的用例。Runnable
接口不会返回结果或者抛出检查异常,但是Callable
接口可以。
工具类Executors
可以实现Runnable
对象和Callable
对象的相互转换。
execute()方法和submit()方法的区别
execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否。
submit()
方法用于提交需要返回值的任务,线程池会返回一个Future
类型的对象,通过这个Future
对象可以判断任务是否执行成功。可以通过Future
的get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成,而get(long timeout, TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
线程池如何创建
《阿里巴巴Java开发手册》中强制线程池不允许使用Executors
去创建,而是通过ThreadPoolExecutor
的方式,这中强制要求的目的在于让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors
返回线程池对象的弊端如下:
FixedThreadPool
和SingleThreadExecutor
:允许请求的队列长度为Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致OOM
。CacheThreadPool
和ScheduledThreadPool
:允许请求的线程数量为Integer.MAX_VALUE
,可能会创建大量线程,从而导致OOM
。
方法一:通过ThreadPoolExecutor
构造方法实现
ThreadPoolExecutor
ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue<Runnable>)
ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue<Runnable>, ThreadFacory)
ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue<Runnable>, RejectedExecutionHandler)
ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue<Runnable>, ThreadFctory, RejectedExecutionHandler)
方法二:通过Executor框架的工具类Executors
来实现
该方法可以创建四种类型的ThreadPoolExecutor
:
FixedThreadPool
:该方法返回固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。SingleThreadExecutor
:方法返回仅有一个线程的线程池。若有多于一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。CachedThreadPool
:该方法返回一个可根据实际情况调整线程数量的线程池,线程数量不确定,若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。ScheduledThreadPool
:方法返回一个能实现定时、周期性任务的线程池。主要用于给定延时之后的运行任务或者定期处理任务。
以上四个类对应的Executor
全部通过调用父类ThreadPoolExecutor
的构造方法来实现
ThreadPoolExecutor构造函数分析(重要)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFctory threadFactory, RejectedExecutionHandler handler)
ThreadPoolExecutor3个最重要的参数:
corePoolSize
:核心线程数,定义了最小可以同时运行的线程数。maximumPoolSize
:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。workQueue
:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
ThreadPoolExecutor其他常见参数
keepAliveTime
:当线程池中的线程数量大于corePoolSize
的时候,如果没有新任务提交,核心线程之外的线程并不会被立即销毁,而是会等待,等待时间超过了keepAliveTime
才会被回收销毁。unit
:指定keepAliveTime
参数的时间单位。threadFactory
:executor创建新线程时候会用到。handler
:饱和策略,当线程池达到饱和状态时,采用的策略。
ThreadPool饱和策略
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,可以使用ThreadPoolTaskExecutor
定义的一些策略:
ThreadPoolExecutor.AbortPolicy
:抛出RejectedExecutionException
来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy
:调用执行自己的线程运行任务。该策略不会进行任务请求。但是这种策略会降低新任务的提交速度,影响程序的整体性能。另外,该策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且不能丢弃任何一个任务请求的话,你可以选择这个策略。ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy
:将丢弃最早的未处理的任务请求。
参考
JavaGuide面试突击版,百度可得最新版本,有删减、增加和修正。