平时讨论多线程处理,一定会说使用线程池,那为什么要使用线程池呢?其实,这个问题也可以反过来思考一下,如果不使用线程池会怎么样?
当需要多线程并发执行任务时,只能不断的通过new Thread创建线程,每创建一个线程都需要在堆上分配内存空间,同时需要分配虚拟机栈,本地方法栈,程序计数器等线程私有的内存空间,当这个线程对象被可达性分析算法标记为不可用时,被GC回收,这样频繁的创建和回收需要大量的额外开销。再者说,JVM的内存资源是有限的,如果系统中大量的创建线程对象,JVM很可能直接抛出OutOfMemoryError异常,还有大量的线程去竞争CPU会产生其他的性能开销,更多的线程反而会降低性能,所以必须要限制线程数。
既然不使用线程池会有那么多问题,我们来看一下使用线程池有哪些好处:
1.使用线程池可以复用池中的线索,不需要每次都创建新线程,减少创建和销毁线程的开销。
2.同时,线程池具有队列缓冲策略,拒绝机制和动态管理线程个数,特定的线程池还具有定时执行,周期执行功能,比较重要的一点是线程池可以实现线程环境的隔离,例如分别定义支付功能相关线程池和优惠券功能相关线程池,当其中一个运行有问题时不会影响另一个。
一个线程池里面有多个线程,因此我们后续都是用线程池对象的方法进行多线程操作的,这样我们只针对线程池对象就行了,不必再针对线程对象了,因此我们就不用频繁的创建销毁线程了。
如下图:
Running:线程池运行状态
Shutdown状态:线程池不会再接收新的任务,但会处理阻塞队列剩余任务
Stop状态:会中断正在执行的任务,并抛弃阻塞队列任务
Tidying状态:任务全部执行完毕,活动线程为0即将进入终结
Terminated状态:终结状态。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize 核心线程数目,最多保留的线程数
maximumPoolSize 最大线程数目
keepAliveTime 生存时间-针对救急线程
unit 时间单位-针对救急线程
workQueue 阻塞队列
threadFactory 线程工厂-可以为线程创建时起个好名字
handler 拒绝策略
最大线程数=核心线程数+救急线程数,其中核心线程不会消失永远存在,而救急线程是会消失的。
newFixedThreadPool方法可以在线程池里面创建固定的线程个数,如下图:
当我们调用我们的线程池执行一个任务的时候,线程池会调用它里面的线程此任务,如下图:
//执行任务
void execute(Runnable command);
//提交任务task,用返回值Future获得任务执行结果
<T> Future<T> submit(Callable<T> task);
//提交tasks中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
//提交tasks中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extend Callable<T>> tasks,
long timeout,TimeUnit unit)
throws InterruptedException;
//提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
//提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消,带超时时间
<T> T invokeAny(Collection<>? extends Callable<T>> tasks,
long timeout,TimeUnit unit)
throws InterruptedException;
当我们使用submit给线程池提交任务的时候,线程池会用它里面的不同的线程去并发执行这些任务,如下图:
submit方法提交任务的时候,有两种类型的参数,一种是Callable线程任务,这种任务有返回值并且可以内部抛异常,另外一种是Runnable线程任务,这种任务没有返回值并且不可以在内部抛异常。但是Callable和Runnable对象都属于是多线程任务对象。Callable线程对象对应的线程方法是call方法,而Runnable线程对象对应的方法是run方法。
对于任务来讲,只要它一提交到线程池里面,它就会被自动的用线程池里面的线程执行了。
使用submit提交的线程任务,一般会在下面的普通代码执行之后执行,如下图:
/**
线程池状态变为 shutdown
不会接收新任务
但已提交的任务会执行完
此方法不会阻塞调用线程的执行
*/
void shutdown();
/**
线程池状态变为stop
不会接收新任务
会将队列中的任务返回
并用interrupt的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();
使用偷窃线程池的时候,线程池中的每个线程都有一个任务队列,线程之前可以互相帮助,如果一个线程的工作已经干完了,发现另外一个线程的工作还没有干完,那么这个线程会去帮另外一个线程工作。这个线程池非常适合于耗时较长的任务。