线程池
继承Thread的弊端
1.每次new Thread的时候都需要新建一个线程,性能差
2.线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM
3.Thread类缺少更多功能,比如更多的执行、定期执行、线程中断。
线程池的好处
1.重用存在的线程,减少对象创建、消亡的开销、性能佳
2.可以有效的控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
3.提供定时执行、定期执行、单线程、并发数控制等功能。
线程池-ThreadPoolExecutor
参数说明:ThreadPoolExecutor一共有七个参数,这七个参数配合起来,构成了线程池强大的功能。
corePoolSize:核心线程数线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。
maximumPoolSize:线程最大线程数,线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。
workQueue : 阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响
ThreadPoolExecutor执行execute方法分下面4种情况。
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
线程池大小调节策略。通过corePoolSize和maximumPoolSize,控制如何新增线程;通过allowCoreThreadTimeOut和keepAliveTime,控制如何销毁线程。
allowCoreThreadTimeOut:
该属性用来控制是否允许核心线程超时退出。如果线程池的大小已经达到了corePoolSize,不管有没有任务需要执行,线程池都会保证这些核心线程处于存活状态。可以知道:该属性只是用来控制核心线程的。
keepAliveTime:
线程超时退出时间。举个例子,如果线程池的核心大小corePoolSize=5,而当前大小poolSize =8,那么超出核心大小的线程,会按照keepAliveTime的值判断是否会超时退出。如果线程池的核心大小corePoolSize=5,而当前大小poolSize =5,那么线程池中所有线程都是核心线程,这个时候线程是否会退出,取决于allowCoreThreadTimeOut。
unit : keepAliveTime的时间单位
threadFactory:线程工厂,用来创建线程
rejectHandler:当拒绝处理任务时的策略
四种拒绝策略:
AbortPolicy :直接抛出异常
CallerRunsPolicy :用调用者所在的线程来执行任务
DiscardOldestPolicy :线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy :不处理,直接丢弃
- running:能接受新提交的任务,也能处理阻塞队列中的任务
- shutdown:不能处理新的任务,但是能继续处理阻塞队列中任务
- stop:不能接收新的任务,也不处理队列中的任务
- tidying:如果所有的任务都已经终止了,这时有效线程数为0
- terminated:最终状态
序号 | 方法名 | 描述 |
---|---|---|
1 | execute() | 提交任务,交给线程池执行 |
2 | submit() | 提交任务,能够返回执行结果 execute+Future |
3 | shutdown() | 关闭线程池,等待任务都执行完 |
4 | shutdownNow() | 关闭线程池,不等待任务执行完 |
5 | getTaskCount() | 线程池已执行和未执行的任务总数 |
6 | getCompleteTaskCount() | 已完成的任务数量 |
7 | getPoolSize() | 线程池当前的线程数量 |
8 | getActiveCount() |
当前线程池中正在执行任务的线程数量 |
使用Executor创建线程池
使用Executor可以创建四种线程池:分别对应上边提到的四种线程池初始化方法
1、Executors.newCachedThreadPool
- 它是一个可以无限扩大的线程池;
- 它比较适合处理执行时间比较小的任务;
- corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
- keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
- 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
//使用方法: public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
值得注意的一点是,newCachedThreadPool的返回值是ExecutorService类型,该类型只包含基础的线程池方法,但却不包含线程监控相关方法,因此在使用返回值为ExecutorService的线程池类型创建新线程时要考虑到具体情况。
2、newFixedThreadPool
定长线程池,可以线程现成的最大并发数,超出在队列等待
- 它是一种固定大小的线程池;
- corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
- keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;
- 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
- 由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;
- 由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。
//使用方法: public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
3、newSingleThreadExecutor
单线程化的线程池,用唯一的一个共用线程执行任务,保证所有任务按指定顺序执行(FIFO、优先级…)
- 它只会创建一条工作线程处理任务;
- 采用的阻塞队列为LinkedBlockingQueue;
//使用方法: public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
4、newScheduledThreadPool
定时线程池,支持定时和周期任务执行
//基础使用方法: public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); executorService.schedule(new Runnable() { @Override public void run() { log.warn("schedule run"); } }, 3, TimeUnit.SECONDS);//延迟3秒执行 executorService.shutdown(); }
小扩展:延迟执行任务的操作,java中还有Timer类同样可以实现
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { log.warn("timer run"); } }, new Date(), 5 * 1000);