目录
一、线程池
1、为什么使用线程池
使用多线程,可以帮助我们很好的解决一些并发环境下的问题,提升程序运行速度。
但如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,占用大量系统资源,可能会造成系统奔溃。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
线程池出现了。
2、线程池概念
在系统启动时即创建大量空闲的线程,这些线程的集合称为线程池。程序将一个任务传给线程池,线程池拿到任务后,就查询是否有空闲的线程,如果有,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
一个线程只能执行一个任务,但是一个线程池可以处理多个任务请求。
3、线程池初始化及线程池的状态
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
- prestartCoreThread():初始化一个核心线程;
- prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
public boolean prestartCoreThread() {
return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
public int prestartAllCoreThreads() {
int n = 0;
while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
++n;
return n;
}
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState; //当前线程池的状态,volatile变量用来保证线程之间的可见性
static final int RUNNING = 0; //初始
static final int SHUTDOWN = 1; //此时线程池不能够接受新的任务,它会等待所有任务执行完毕
static final int STOP = 2; //此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务
static final int TERMINATED = 3;//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态
调用了shutdown()方法,则线程池处于SHUTDOWN状态;
调用了shutdownNow()方法,则线程池处于STOP状态;
4、线程池核心类ThreadPoolExecutor
以下是ThreadPoolExecutor类的四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
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);
}
下面解释下一下构造器中各个参数的含义:
corePoolSize :核心线程数,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize : 最大线程数
keepAliveTime :当线程数大于核心线程时,此为终止前多余的空闲线程等待新任务的最长时间
unit :时间单位,有7种取值,在TimeUnit类中有7种静态属性
workQueue :一个阻塞队列,用来存储等待执行的任务,一般workQueue有以下几种方式
队列 | 解释 | 线程安全 |
---|---|---|
ArrayBlockingQueue | 基于数组的先进先出,创建时必须指定大小,超出核心线程数corePoolSize的任务,则加入到该队列中,只能加该队列设置的大小,其余的任务则创建新线程,直到(corePoolSize+新建线程)> maximumPoolSize | 线程安全 |
LinkedBlockingQueue | 基于链表的先进先出,无界队列,所以maximumPoolSize不起作用。超出核心线程数corePoolSize的任务,则加入到该队列中,直到资源耗尽 | 线程安全 |
SynchrousQueue | 不会保存提交任务,直接提交,超出核心线程数corePoolSize的任务,直接创建新的线程来执行任务,直到(corePoolSize+新建线程)> maximumPoolSize。 | 线程安全 |
threadFactory : 创建线程的工厂
handler :由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
5、四种常见的线程池实现方式
以下四种方法,都是Executors提供的静态方法,一般情况下,如果这几种方法可以满足您的需求,就尽量使用它提供的方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
线程池的返回值ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程
(1)Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务。
运行结果:
(2)Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
运行结果:
线程池大小为3,每个任务输出打印结果后sleep 1秒,所以每1秒打印3个结果。定长线程池的大小最好根据系统资源进行设置。
(3)Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行。
(4)Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
运行结果:
四种线程池实现方式都是通过图片展现给各位老爷,就是希望大家可以自己敲下代码,加深印象,而不是做代码的搬运工。
内容如有疑问,请提出宝贵意见,互相交流探讨。
参考资料:https://www.cnblogs.com/dolphin0520/p/3932921.html
https://www.cnblogs.com/xiguadadage/p/10243332.html
https://blog.csdn.net/nihaomabmt/article/details/81667481