目录
我们为什么要用线程池?
线程池的优势
(1)
降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom
等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
(4)更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
如何创建一个线程池?
通过ThreadPoolExecutor来创建一个线程池
//不同参数
ExecutorService service = new ThreadPoolExecutor(....);
//七个参数
ExecutorService service = new ThreadPoolExecutor(5, 10, 10, Time Unit.SECONDS, new LinkedBlockingQueue<>());
//线程池的7个参数的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
ThreadPoolExecutor参数含义
1. corePoolSize
线程池中的核心线程数,默认情况下,核心线程一直存活在线程池中,即便他们在线程池中处于闲置状态。除非我们将ThreadPoolExecutor
的allowCoreThreadTimeOut属性设为
true
的时候,这时候处于闲置的核心线程在等待新任务到来时会有超时策略,这个超时时间由keepAliveTime
来指定。一旦超过所设置的超时时间,闲置的核心线程就会被终止。
2. maximumPoolSize
线程池中所容纳的最大线程数,如果活动的线程达到这个数值以后,后续的新任务将会被阻塞。包含核心线程数+
非核心线程数。
3. keepAliveTime
非核心线程闲置时的超时时长,对于非核心线程,闲置时间超过这个时间,非核心线程就会被回收。只有对ThreadPoolExecutor
的
allowCoreThreadTimeOut
属性设为true
的时候,这个超时时间才会对核心线程产生效果。
4. unit
用于指定
keepAliveTime
参数的时间单位。他是一个枚举,可以使用的单位有:
天 (TimeUnit.DAYS
),
小时(
TimeUnit.HOURS
),
分钟(TimeUnit.MINUTES
),
毫秒(
TimeUnit.MILLISECONDS)
,
微秒(TimeUnit.MICROSECONDS, 千分之一毫秒),
毫微秒(TimeUnit.NANOSECONDS, 千分之一微秒);
5. workQueue
线程池中保存等待执行的任务的阻塞队列。通过线程池中的
execute
方法提交的Runable对象都会存储在该队列中。我们可以选择下面几个阻塞队列。
![](https://i-blog.csdnimg.cn/blog_migrate/d0d66581f8061c2beb6d299f39cdfe9a.png)
6. threadFactory
线程工厂,为线程池提供新线程的创建。
ThreadFactory
是一个接口,里面只有一个newThread
方法。 默认为
DefaultThreadFactory
类。
7. handler
是
RejectedExecutionHandler
对象,而
RejectedExecutionHandler
是一个接口,里面只有一个rejectedExecution
方法。当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者是无法成功执行任务,这时候ThreadPoolExecutor
会调用RejectedExecutionHandler
中的
rejectedExecution
方法。在ThreadPoolExecutor中有四个内部类实现了
RejectedExecutionHandler
接口。在线程池中它默认是AbortPolicy
,在无法处理新任务时抛出
RejectedExecutionException
异常。
下面是在
ThreadPoolExecutor
中提供的四个可选值。
![](https://i-blog.csdnimg.cn/blog_migrate/627a35a9184bd8042147267048e892e9.png)
如何使用线程池?
可以通过
execute
和
submit
两种方式来向线程池提交一个任务。
(1)execute
当我们使用
execute
来提交任务时,由于
execute
方法没有返回值,所以说 我们也就无法判定任务是否被线程池执行成功。
service.execute(new Runnable() {
public void run() {
System.out.println("execute方式");
}
});
(2)submit
当我们使用
submit
来提交任务时
,
它会返回一个
future,
我们就可以通过这个
future
来判断任务是否执行成功,还可以通过future
的
get
方法来获取返回值。如果子线程任务没有完成,get
方法会阻塞住直到任务完成,而使用
get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时候有可能任务并没有执行完。
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("submit方式");
return 2;
}
});
try {
Integer number = future.get();
} catch (ExecutionException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
使用完后如何关闭线程池?
线程池关闭
调用线程池的
shutdown()
或
shutdownNow()
方法来关闭线程池
(1)shutdown原理:
将线程池状态设置成
SHUTDOWN
状态,然后中断所有没有正在执行任务的线程。
(2)shutdownNow原理:
将线程池的状态设置成
STOP
状态,然后中断所有任务
(
包括正在执行的)
的线程,并返回等待执行任务的列表。
中断采用
interrupt
方法,所以无法响应中断的任务可能永远无法终止。 但调用上述的两个关闭之一,isShutdown()
方法返回值为
true
,当所有任务都已关闭,表示线程池关闭完成,则isTerminated()
方法返回值为
true
。当需要立刻中断所有的线程,不一定需要执行完任务,可直接调用shutdownNow()
方法。