本文共 6305 字,大约阅读时间需要 21 分钟。
文章目录
1. 线程池
创建线程的方式
- 继承 Thread
- 实现 Runnable
- 实现 Callable
- 线程池(推荐使用线程池来创建线程)
线程池概述:
程序启动一个新的线程的成本是比较高的,因为它要与操作系统来交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生命周期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程在调用结束后,并不会死亡,而是再次回到线程池中成为空闲状态,再次等待调度。在JDK5之前,线程池必须手动来创建;JDK5之后,Java内置线程池来直接使用。
线程池的三个优点如下:
- 降低资源消耗: 通过重复利用已创建的线程,降低线程创建和销毁带来的开销
- 提高响应速度: 当任务到达时,任务可以不需要等待线程的创建能立即执行(线程池中存在已经创建好的线程来直接使用)
- 提高线程的可管理性: 使用线程池可以统一进行线程分配、调度和监控
我们在线程池中创建两个线程来执行三个线程任务
线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用2. 线程池核心接口
ExecutorService 普通线程池
向线程池提交任务:
execute(Runnable command)
submit(Collable<T> task || Runnable)
ScheduledExecutoService 定时线程池
scheduleAtFixedRate()
ThreadPoolExecutor 线程池核心类
ThreadPoolExecutor : ExecutorService 的子类
ThreadPoolExecutor(int corePoolSize,intmaximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueueworkQueue, RejectedExecutionHandler handler)
3. 线程池工作原理
线程池工作流程:
当一个任务提交给线程池时 ---->
核心线程池 corePoolSize
最大线程池 maximumPoolSize
阻塞队列 BlockingQueue
拒绝策略 RejectPolicy
- 首先判断核心池的线程数量是否达到 corePoolSize. 若未达到,线程池创建新的线程执行任务并将其置入核心池中; 否则,判断核心线程池是否有空闲线程. 若有,分配任务执行; 否则,进入步骤2
- 判断当前线程池中线程数量有没有达到线程池的最大数 maximumPoolSize. 若没有,创建新的线程执行任务并将其置入线程池中; 否则,进入步骤3
- 判断阻塞队列是否已满. 若未满,将任务置入阻塞队列中等待调度. 否则,进入步骤4
- 调用相应的拒绝策略打回任务.(有四种拒绝策略,默认抛出异常给用户 AbortPolicy)
4. java中常见的阻塞队列
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,静态工厂方法Executors.newFixedThreadPool()使用了这个队列
- 内置线程池 FixedThreadPool, SingleThreadPool
- SynchronousQueue:一个不存储元素的阻塞队列
- 内置线程池 CachedThreadPool (一个元素的插入必须等待同时有一个元素的删除操作,否则插入操作就一直阻塞)
- PriorityBlockingQueue: 基于优先级的阻塞队列
5. 创建线程池以及向线程池提交任务
手工创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 3, 5, 2000,TimeUnit.MILLISECONDS, new LinkedBlockingDeque());
可以使用两个方法向线程池提交任务,分别为execute() 和 submit()方法
execute()
方法用于提交不需要返回值的任务; 所以无法判断任务是否被线程池执行成功submit()
方法用于提交需要返回值的任务; 线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
线程中提交方法 | 传入参数 |
---|---|
execute() | Runnable |
submit() | Runnable 或 Callable |
package com.ThreadPool;import java.util.concurrent.*;/** * @Author: Mr.Q * @Date: 2019-08-05 08:05 * @Description: */class ThreadPoolTest implements Callable提交了5次任务,但是 ThreadPool 创建了两个线程;{ private Integer tickets = 20; @Override public String call() throws Exception { for (int i = 0; i < tickets; i++) { if(tickets > 0) System.out.println(Thread.currentThread().getName() +"还剩 "+tickets--+" 票..."); } return Thread.currentThread().getName() + "票卖完了!"; }}public class ExecutorTest { public static void main(String[] args) { ExecutorService executor = new ThreadPoolExecutor(2,3, 60,TimeUnit.SECONDS, new LinkedBlockingQueue<>()); ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for(int i = 0; i < 5; i ++) { executor.submit(threadPoolTest); //submit可以提交Callable,Runnable接口 } executor.shutdown(); }}
原因是核心线程池未满,有空闲线程;由于实现 Callable接口只做了 System.out
,线程拿到任务很快执行完成;执行任务的速度快于提交任务的速度,就不在创建线程了…
线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力
6. 合理配置线程池
查看当前电脑CPU数目:
System.out.println(Runtime.getRuntime().availableProcessors());
输出的为当前电脑可执行的线程数目,目前默认一个CPU可以同时执行俩个线程,CPU数为线程数的一半
配置核心池以及最大线程池线程数量
- CPU密集型任务:nCPU + 1
- IO密集型任务: 2 * nCPU
CPU密集型任务 : 频繁操作CPU(大数运算)
IO密集型任务 : 频繁操作文件(读写文件)
7. JDK 内置的四大线程池 Executor
Executor 框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。通过Executor框架的工具类Executors,可以创建3种类型的 ThreadPoolExecutor
1.缓存线程池(无大小限制的线程池):newCachedThreadPool()
使用场景:适用于负载较轻的服务器,或执行很多短期的异步任务
ExecutorService executorService = Executors.newCachedThreadPool();public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }
任务提交速度 > 线程执行速度 | 会不断创建线程(有可能无限创建线程将内存写满) |
---|---|
任务提交速度 < 线程执行速度 | 只会创建若干线程 |
2.固定大小线程池:newFixedThreadPool(int nThreads)
使用场景: 适用于负载较重的服务器,来满足资源分配的要求
ExecutorService executorService = Executors.newFixedThreadPool();public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }
3.单线程池:newSingleThreadExecutor()
使用场景: 多线程场景下需要让任务串行执行
ExecutorService executorService = Executors.newSingleThreadExecutor();public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); }
4.定时调度池: newScheduledThreadPool();
使用场景:ScheduledExecutorService是java.util.concurrent并发包下的一个接口,表示调度服务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool();public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
定时调度池中的方法
scheduled.schedule();
延迟 delay个时间单元后创建 nThreads个线程执行 command任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(); scheduled.schedule(); public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit);
scheduled.scheduleAtFixedRate();
延迟 delay个时间单元后每隔 period time执行一次 command任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(3); scheduled.scheduleAtFixedRate();public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
scheduled.scheduleWithFixedDelay();
scheduled.scheduleAtFixedRate();
和 scheduled.scheduleWithFixedDelay();
都表示每间隔一段时间定时执行任务
- scheduleAtFixedRate是以上一次任务的开始时间为间隔的,并且当任务执行时间大于设置的间隔时间时,真正间隔的时间由任务执行时间为准
- scheduleWithFixedDelay是以上一次任务的结束时间为间隔
中方法详解(参考)
转载地址:https://iqqcode.blog.csdn.net/article/details/98971298 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!