多线程之线程池原理分析

线程池存在的意义

1.线程池保持着一定数量的线程一直活着,这样可以避免高频率的创建和销毁线程
1)创建和销毁一个线程都会有两个动作:给线程申请和回收内存上下文空间;以及需要cpu为线程执行
2)创建线程过多:计算机的内存空间会被大量的线程上下文占用;cpu无法及时处理每个线程任务,实时性降低
3)创建线程过少:cpu容易因为大部分线程阻塞而导致cpu空闲等待(例如磁盘IO和网络IO阻塞)
4)基于以上原因,计算机维持一定数量的线程才能充分而高效的让系统调度计算机的资源
5)在高并发场景,如果短时间内创建和销毁大量线程,甚至会引发资源(内存,端口号)不足而导致系统崩溃
6)最感人的场景:线程指令执行时间 < (线程指令创建时间+线程指令销毁的时间)
2.线程池的线程的意义
1)线程复用:执行线程任务,无需每次创建线程和为线程申请内存上下文空间
2)避免高并发场景的尴尬:由于线程数量一定,不会出现计算机资源不足的场景
3)避免高并发场景的尴尬:部分线程能及时响应客户端,而不会导致所有线程的无法及时响应任务(线程任务排队执行)

Java中的线程池顶级接口

1.ThreadPoolExecutor和ScheduledThreadPoolExecutor架构图
在这里插入图片描述
2.Executor顶层接口,只有一个方法execute(Runnable):该方法能执行Runnable实现类的逻辑,但是没有返回值
3.ExecutorService继承于Executor接口
1)ExecutorService目的为了增强Executor接口,增加了几个方法,可以获取和控制线程池的一些当前状态和行为
2)submit(Runnable或Callable)方法:方法返回Future对象,Future.get()方法可以阻塞提交线程,并且可获得提交线程的返回值和状态
3)invokeAny(Runnable[]或Callable[])方法:随机执行一个线程,并返回一个Future对象
4)invokeAll(Runnable[]或Callable[])方法:执行所有线程,并返回一个Future[]数组
5)shutdown():调用接口之后,意味着线程池不再处理任何新的线程任务,把正在执行的线程处理完毕,就kill掉所有活跃线程
6)shutdownNow():调用接口之后,就kill掉所有活跃线程,并且正在执行线程也kill掉(线程任务的逻辑可能只执行到一半)
7)isShutdown():线程池是否已经完全kill掉所有活着线程(线程池处于关闭状态)
8)isTerminated():线程池中所有的工作任务是否已经完全执行
9)awaitTermination(5秒): 阻塞当前线程,等待线程池5秒内完成所有任务,若不能完成,返回false,此时需要强制手动关闭线程池

Callable和Future的关系

1.Future的默认实现类是FutureTask,一个Callable任务会对应一个FutureTask
1)FutureTask实现了Runnable和Future接口,所以FutureTask具备Runnable和Future的功能
2)Future接口接口有如下方法
3)get():获取Callable线程的返回结果
4)get(5秒):线程任务最多在给定时间内完成,如果不能及时完成会被抛出TimeoutException异常
5)cancel(true):尝试中断Callable线程的执行,中断成功返回true,反之返回false
6)isCancelled():如果任务在正常结束之前,被取消返回true
7)isDone():正常结束、异常或者被取消导致任务完成,将返回true
2.线程池submit(Callable)方法的具体逻辑
1)方法被调用时,传进去的Callable参数会被封装进FutureTask对象中,成为该对象的一个属性
2)FutureTask实现了Runnable,当Runnable.run()方法被执行,内部逻辑会调用Callable.call()方法
3)当call()方法被调用完成,会得到一个返回结果result,该结果会被设置到:FutureTask.outcome = result
4)设置完结果之后,同时会把FutureTask.state设置为已完成状态
5)当外部调用Future.get()方法时,会通过FutureTask.state来判断是否已经完成了线程任务,若完成就直接返回FutureTask.outcome
6)补充:该FutureTask对象会被当成一个正常的Runnable线程任务,该任务会被线程池的execute(Runnable)方法执行

ThreadPoolExecutor核心接口案例

1.ThreadPoolExecutor实现ExecutorService和Executor接口的方法
1)向线程池提交线程任务的方法有两个:Executor.execute(Runnable)和ExecutorService.submit(Callable)
2)Executor.execute(Runnable):所有线程任务的提交最终都会执行到该方法,其他的提交方法只是在这之前做一些增强操作
3)ExecutorService.submit(Callable):该方法会把Callable参数包装成了FutureTask对象,然后再执行execute(FutureTask)
4)使用FutureTask对象包装Callable接口,只是为了做一层缓存:缓存call()方法的返回结果和缓存任务线程的当前处理状态
2.Executor.execute(FutureTask)主要逻辑
在这里插入图片描述

1)调用addWorker(FutureTask,true):判断核心线程数是否已经已满,未满,开启新的核心线程处理任务
2)若已满,则调用workQueue.offer(FutureTask):把线程任务缓存到工作队列中workQueue,若缓存失败,则进入下一步流程
3)调用addWorker(FutureTask,false):判断最大线程数是否已满,未满,开启新的普通线程处理任务
4)若已满,则根据不同的拒绝策略,拒绝当前提交的线程任务执行
5)注意:具体的任务执行逻辑在addWorker(FutureTask,true)方法,参数true标识是核心线程,false标识是普通线程
3.详说addWorker(FutureTask,true)方法
1)把FutureTask线程任务包装成Worker对象,并可从Worker对象中取出一个可执行线程Worker.thread
2)其实Worker.thread就是指向Worker对象本身,Worker类是实现了Runnable接口,并且Worker是线程池的内部类
3)当调用Worker.thread.start()方法就是调用Worker.run()方法(核心线程逻辑:处理一次FutureTask和循环处理workQueue任务)
4)由上可知:Worker.run()方法能访问FutureTask对象(Worker包装了FutureTask)和workQueue任务(Worker是线程池的内部类)
5)上文提到的访问是指:Worker.run()方法内部手动调用FutureTask.run()方法以及工作队列中线程任务的run()方法
6)整体调用逻辑:Worker.thread.start()–》Worker.run()–》FutureTask.run()或工作队列中线程任务的run方法
4.Worker.run()的具体运行逻辑
在这里插入图片描述
1)由上图可知,task就是指FutureTask和workQueue的线程任务
2)启动线程通过循环处理task任务,处理方式就是手动调用task.run()方法
3)启动线程退出循环的情况:外部调用线程池的shutdown()方法;调用getTask()方法时,得到返回值null
4)注意:getTask()方法意味着可以控制while跳出循环,这也是线程池回收线程的关键所在

线程池思考带来的问题

1.核心线程如何保持活跃不死
1)核心线程指的是Worker对象,Worker实现了Runnable接口
2)调用Worker.start()方法,就是调用Worker.run()方法
3)Worker.run()方法会while循环处理工作队列的线程任务
2.如何回收普通线程
1)上文提过回收线程的关键在于getTask()方法,通过返回值(是否为null)的方式,控制开启的线程是否退出
2)getTask()方法的主要逻辑:
3)第一:判断当前开启的线程数是否大于核心线程数,若是,则设置一个标志位timed=true
4)第二:若标志位timed为true,意味着当前开启的线程的空闲时间超过一定时,就会进行当前线程退出
5)第三:当前线程退出的方式,就是在getTask()方法直接return一个null值
6)第四:判断超时的逻辑在于下面的代码:
在这里插入图片描述
7)第五:timed为true时,调用workQueue.poll(5秒)获取线程任务,在5秒内获取不到,此时意味着当前线程已经空闲超时
8)第六:空闲超时之后,就可以为getTask()方法直接return一个null值,然后当前线程退出while循环,达到回收线程的目的
9)注意:若timed为false时,调用的是workQueue.take()方法,获取不到线程任务,当前线程就会一直阻塞直到队列返回线程任务
3.FutureTask如何取消正在运行的线程
1)判断FutureTask线程任务是否已经被开启的Worker线程处理
2)若是,调用FutureTask.cancel(true)方法时,获取启动的Worker线程,调用interrupt()方法并尝试中断正在运行的线程


版权声明:本文为weixin_39296289原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。