当前位置:网站首页>线程池的使用
线程池的使用
2022-07-05 20:36:00 【菜鸟xiaowang】
1.线程池使用场景
java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。
线程池可以:
加快请求响应(响应时间优先)
加快处理大任务(吞吐量优先)
使用过程
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 30, TimeUnit.SECONDS, new SynchronousQueue<>());
//向线程池提交任务 无返回值
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
}
});
//向线程池提交任务 有返回值
Future<String> submit = threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("threadPoolExecutor submit");
return "null";
}
});
//返回任务的执行结果
try {
Object o = submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭线程池
threadPoolExecutor.shutdown();
线程池的创建
线程池可以自动创建也可以手动创建,
自动创建
自动创建体现在Executors工具类中,常见的可以创建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool;
//自动创建不同类型的线程池,使用Executors类,指定的参数比较少
//手动创建使用 ThreadPoolExecutor ,可以指定多个参数
//创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("延迟四秒执行");
}
},4, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟1S后,每隔3秒执行一");
}
},1,3,TimeUnit.SECONDS);
//返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)
// 重新启动一个线程来替代原来的线程继续执行下去
ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
ExecutorService executorService1 = Executors.newFixedThreadPool(3);
//创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们
ExecutorService executorService2 = Executors.newCachedThreadPool();
手动创建
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
手动创建体现在可以灵活设置线程池的各个参数,体现在代码中即ThreadPoolExecutor类构造器上各个实参的不同:
//手动创建线程池
/*
corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
unit:keepAliveTime的时间单位
workQueue:用于保存任务的队列,可以为无界、有界、同步移交new SynchronousQueue<>()三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 30, TimeUnit.SECONDS, new SynchronousQueue<>());
workQueue队列
SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致内存占用过多或OOM
ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务
handler拒绝策略
- AbortPolicy:中断抛出异常
- DiscardPolicy:默默丢弃任务,不进行任何通知
- DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
- CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)
关闭线程池
shutdownNow():立即关闭线程池(暴力),正在执行中的及队列中的任务会被中断,同时该方法会返回被中断的队列中的任务列表
shutdown():平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
isTerminated():当正在执行的任务及对列中的任务全部都执行(清空)完就会返回true
线程池线程复用原理
1.线程池里执行的是任务,核心逻辑在ThreadPoolExecutor类的execute方法中,同时ThreadPoolExecutor中维护了HashSet<Worker> workers;
2.addWorker()方法来创建线程执行任务,如果是核心线程的任务,会赋值给Worker的firstTask属性;
3.Worker实现了Runnable,本质上也是任务,核心在run()方法里;
4.run()方法的执行核心runWorker(),自旋拿任务while (task != null || (task = getTask()) != null)),task是核心线程Worker的firstTask或者getTask();
5.getTask()的核心逻辑:
1.若当前工作线程数量大于核心线程数->说明此线程是非核心工作线程,通过poll()拿任务,未拿到任务即getTask()返回null,然后会在processWorkerExit(w, completedAbruptly)方法释放掉这个非核心工作线程的引用;
2.若当前工作线程数量小于核心线程数->说明此时线程是核心工作线程,通过take()拿任务
3.take()方式取任务,如果队列中没有任务了会调用await()阻塞当前线程,直到新任务到来,所以核心工作线程不会被回收; 当执行execute方法里的workQueue.offer(command)时会调用Condition.singal()方法唤醒一个之前阻塞的线程,这样核心线程即可复用
Callable和Runnable
Runnable和Callable都可以理解为任务,里面封装着任务的具体逻辑,提交给线程池执行,区别在于Runnable任务执行没有返回值,且Runnable任务逻辑中不能通过throws抛出cheched异常(但是可以try catch),而Callable可以获取到任务的执行结果返回值且抛出checked异常。
Future和FutureTask
Future接口用来表示执行异步任务的结果存储器,当一个任务的执行时间过长就可以采用这种方式:把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果。
Future的主要方法包括:
get()方法:返回任务的执行结果,若任务还未执行完,则会一直阻塞直到完成为止,如果执行过程中发生异常,则抛出异常,但是主线程是感知不到并且不受影响的,除非调用get()方法进行获取结果则会抛出ExecutionException异常;
get(long timeout, TimeUnit unit):在指定时间内返回任务的执行结果,超时未返回会抛出TimeoutException,这个时候需要显式的取消任务;
cancel(boolean mayInterruptIfRunning):取消任务,boolean类型入参表示如果任务正在运行中是否强制中断;
isDone():判断任务是否执行完毕,执行完毕不代表任务一定成功执行,比如任务执行失败但也执行完毕、任务被中断了也执行完毕都会返回true,它仅仅表示一种状态说后面任务不会再执行了;
isCancelled():判断任务是否被取消;
边栏推荐
猜你喜欢
Abbkine BCA法 蛋白质定量试剂盒说明书
Abnova丨 MaxPab 小鼠源多克隆抗体解决方案
Ros2 topic [01]: installing ros2 on win10
Applet page navigation
CTF逆向基础
【数字IC验证快速入门】1、浅谈数字IC验证,了解专栏内容,明确学习目标
Applet global configuration
如何形成规范的接口文档
2022 Beijing eye health products exhibition, eye care products exhibition, China eye Expo held in November
Duchefa丨P1001植物琼脂中英文说明书
随机推荐
Y57. Chapter III kubernetes from entry to proficiency -- business image version upgrade and rollback (30)
Kubernetes resource object introduction and common commands (V) - (configmap & Secret)
Which securities is better for securities account opening? Is online account opening safe?
Frequent MySQL operations cause table locking problems
1、强化学习基础知识点
Duchefa丨S0188盐酸大观霉素五水合物中英文说明书
Schema和Model
Abnova丨荧光染料 620-M 链霉亲和素方案
[quick start to digital IC Verification] 8. Typical circuits in digital ICs and their corresponding Verilog description methods
Codeforces Round #804 (Div. 2) - A, B, C
小程序事件绑定
Leetcode skimming: binary tree 17 (construct binary tree from middle order and post order traversal sequence)
sort和投影
IC popular science article: those things about Eco
关于BRAM IP复位的优先级
【愚公系列】2022年7月 Go教学课程 004-Go代码注释
【数字IC验证快速入门】1、浅谈数字IC验证,了解专栏内容,明确学习目标
mongodb基操的练习
Ros2 topic [01]: installing ros2 on win10
【数字IC验证快速入门】8、数字IC中的典型电路及其对应的Verilog描述方法