线程池

阿里巴巴开发手册中有一条关于使用线程的规定:

1
2
3
【强制】线程资源必须由线程池来提供,不允许在应用中显示的创建线程
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。
如果不使用用线程池,有可能造成系统创建大量同类线程而导致系统消耗完内存或者“过度切换”的问题。

使用线程有如下几个好处:

  1. 线程是稀缺资源,不能频繁的创建
  2. 解耦作用,将线程的创建与执行分开操作。
  3. 提高线程的可管理性。

线程池的实现原理

当提交一个新的任务到线程池时,线程池的处理流程如下:

  1. 线程池判断核心线程池是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程。
  2. 线程池判断工作队列是否已经满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里,如果工作队列满了,则进入下一个流程。
  3. 线程池判断线程池是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务。

image-w100

线程池ThreedPoolExecutor参数简介

四个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)

//六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)

//六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)

//七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

int corePoolSize ==> 该线程池中核心线最大值

当一个任务被提价到线程池中,如果线程池中最大线程数小于核心线程池时,则创建核心线程,如果超过corePoolSize并小于最大线程数,则新建非核心线程。在默认情况下,核心线程在闲置的情况下不会被销毁。但是如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

int maximumPoolSize ==> 该线程池中线程最大总数

线程总数 = 核心线程 + 非核心线程。

long keepAliveTime ==> 非核心线程闲置超时时间

如果非核心线程闲置时间超过该设置的时间,则会被销毁。如果设置allowCoreTreadTimeOut,则会作用于核心线程。

BlockingQueue workQueue ==> 工作队列

当一个任务被提交到线程池中,如果线程数超过核心线程数,则会被提交的工作队列中,如果工作队列中线程数超过最大值时,则会创建非核心线程执行该任务。

向ThreadPoolExecutor添加任务

可以使用两个方法向线程池来提交任务,分别是execute()和submit()方法。

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。通过以下代码可知execute()方法输入的任务是一个Runnable类的实例。

1
2
3
4
5
threadsPool.executor(new Runnable(){
public void run() {
//TODO
}
})

submit()方法用于提交需要返回值的任务。线程吃会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

1
Future<Object> future = executor.submit(harRetureValuetask)

关闭线程池

可以通过调用线程池的shutdown或者shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt放啊发来中断线程,所以无法响应中断的任务可能永远无法终止。

常见的四种线程池

Excutors是Java提供管理线程池的类,改类的两个作用,控制数量和重用线程,通过该类主要有四种线程池的实现方式,这四种线程池底层主要是使用ThreadPoolExcutors类。

  1. CacheThreadPool该线程池内部没有核心线程,线程池的数量没有限制,在创建线程时,若有空闲的线程则服用空闲的线程,若没有则创建新的线程,闲置状态的线程超过了60s没有被使用则会销毁。

    1
    ExecutorService excutor = Executors.newCachedThreadPool();
  2. FixedThreadPool该线程池的最大线程数等于核心线程数,在默认的情况下,该线程池的线程不会为闲置状态超时而被销毁。

    1
    ExecutorService excutor = Executors.newFixedThreadPool(2);
  3. SingleThreadPool有且只有一个工作线程执行任务,所有的线程遵循队列的执行规则。

    1
    ExecutorService excutor = Executors.newSingleThreadExecutor();
  4. ScheduledThreadPool不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE。这个线程池时上述四个中唯一一个有延迟执行和周期执行任务的线程池。

    1
    ExecutorService excutor = Executors.newScheduledThreadPool(1);

线程池隔离

线程池看似很美好,但是也会带来一些问题。

如果我们很多业务都依赖于同一线程池,其中一个业务因为各种不可控的原因消耗了所有的线程,导致线程池全部占满。

这样其他业务就不能运行,这种情况对系统的打击是巨大的。

所以我们需要将线程池进行隔离。通常的做法是根据业务来进行划分:

1
比如下单业务使用一个线程池,获取数据的任务使用一个线程池,这样即使一个任务将线程池的资源消耗尽,也不会对其他任务产生影响。

hystrix 隔离

这样的需要hystrix已经帮我们实现了。

hystrix是一款开源的容错插件,具有依赖隔离,系统降级容错等功能。