一、线程池框架jdk引入版本

jdk1.5引入Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。

二、通过Executors类初始化

newFixedThreadPool()

说明:初始化一个指定线程数的线程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作为阻塞队列

特点:即使当线程池没有可执行任务时,也不会释放线程。

newCachedThreadPool()

说明:初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;

特点:在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致而降低性能。

newSingleThreadExecutor()

说明:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。

特点:如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行;

newScheduledThreadPool()

特点:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。

总结

除了newScheduledThreadPool的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。

三、ThreadPoolExecutor内部具体实现

ThreadPoolExecutor类构造器语法形式

ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,timeUnit,
workQueue,threadFactory,handle);

方法参数:

corePoolSize:核心线程数

maxPoolSize:最大线程数

keepAliveTime:线程存活时间(在corePore<*<maxPoolSize情况下有用

timeUnit:存活时间的时间单位

workQueue:阻塞队列(用来保存等待被执行的任务)workQueue参数的取值,JDK提供了4种阻塞队列类型供选择

ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;

LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue  

SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于ArrayBlockingQuene;

PriorityBlockingQuene:具有优先级的无界阻塞队列;

threadFactory:线程工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略,当线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常;

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

四、线程池的状态(5种)

其中AtomicInteger变量ctl的功能非常强大:利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态

1、RUNNING:-1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;

2、SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;

3、STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
4、TIDYING : 2 << COUNT_BITS,即高3位为010,该状态表示线程池对线程进行整理优化;

5、TERMINATED: 3 << COUNT_BITS,即高3位为011,该状态表示线程池停止工作;

线程状态:
1. 新建状态(New):线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread();
2. 就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked)  : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
  (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
  (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead)    : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

五、向线程池提交任务(2种)

Executor.execute(Runnable command);

ExecutorService.submit(Callable<T> task);

execute()内部实现

1.首次通过workCountof()获知当前线程池中的线程数,如果小于corePoolSize,就通过addWorker()创建线程并执行该任务;否则,将该任务放入阻塞队列;

2.如果能成功将任务放入阻塞队列中,如果当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject()处理该任务;如果当前线程池处于RUNNING状态,则需要再次检查线程池(因为可能在上次检查后,有线程资源被释放),是否有空闲的线程;如果有则执行该任务;

3.如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么将通过addWoker()尝试创建一个新的线程去执行这个任务;如果addWoker()执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;

sumbit()内部实现

会将提交的Callable任务会被封装成了一个FutureTask对象,FutureTask类实现了Runnable接口,这样就可以通过Executor.execute()提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法;

比较:

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。


六、线程池的关闭(2种)

ThreadPoolExecutor提供了两个方法
shutdown()
shutdownNow()

  

1、shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务;

2、shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务


七、线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:
setCorePoolSize()
setMaximumPoolSize()

八、总结:

1、线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果阻塞队列满了,那就创建新的线程执行当前任务;直到线程池中的线程数达到maxPoolSize,这时再有任务来,只能执行reject()处理该任务;

2、如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。