目录
1. run()和start()区别
1)run方法是runnable接口的方法
start方法是thread类的方法
2)run( )是线程操作的核心方法,是每个线程的入口,是由JVM创建完本地操作系统级线程后回调的方法,不可手工调用,否则就是普通方法
start是启动线程的方法,start()调用后,线程的状态由new初始状态到runnable就绪状态
线程在runnable状态下,获取cpu资源后,JVM会回调run(),线程状态由runnable就绪状态到running运行状态。
2. 为什么start()一个线程只能调用一次?
start方法根据threadStatus = 0来判断当前线程是否第一次启动,如果threadStatus不等于0会throw new IllegalThreadStateException();
start( )调用start0():private native void start0();start0()方法更新threadStatus的值
线程第一次调用start(),threadStatus如下:
线程第一次调用start(),调用start0(),threadStatus被更新,如下:
线程第二次调用start(),threadStatus如下:
start0():本地方法,private native void start0(),native方法的注册是在Thread对象初始化的时候由registerNatives()方法完成的
Java有两种方法:Java方法和本地方法。Java方法是由Java语言编写,编译成字节码,存储在class文件中。本地方法是由其他语言(比如C,C++,或者汇编)编写的,编译成和处理器相关的机器代码。本地方法保存在动态连接库中,格式是各个平台专有的。Java方法是平台无关的,单本地方法却不是。运行中的Java程序调用本地方法时,虚拟机装载包含这个本地方法的动态库,并调用这个方法。本地方法是联系Java程序和底层主机操作系统的连接方法。
3.线程池是怎样工作的?
线程池使用了一种池化技术,和很多其他池化技术一样,都是为了更高效的利用资源,例如链接池,内存池等等。
主要从三个方面来对线程池进行分析:线程池状态、重要属性、工作流程
3.1 线程池状态转换
线程池是有状态的,不同状态下线程池的行为是不一样的
RUNNING:运行状态,该状态下线程池可以接受新的任务,也可以处理阻塞队列中的任务;
SHUTDOWN:待关闭状态,不再接受新的任务,继续处理阻塞队列中的任务;
STOP:停止状态,不接收新任务,也不处理阻塞队列中的任务,并且会尝试结束执行中的任务;
TIDYING:整理状态,此时任务都已经执行完毕,并且也没有工作线程;
TERMINATED:终止状态,此时线程池完全终止了,并完成了所有资源的释放。
3.2 重要属性
3.2.1 线程池状态和工作线程数量:ctl
线程池是需要线程去执行具体任务的,所以在线程池中就封装了一个内部类Worker作为工作线程,每个 Worker 中都维持着一个 Thread。线程池的重点之一就是控制线程资源合理高效的使用,所以必须控制工作线程的个数,所以需要保存当前线程池中工作线程的个数。在ThreadPoolExecutor中只用了一个AtomicInteger型的变量就保存了这两个属性的值,那就是 ctl。
高3位用来表示线程池的状态(runState),低29位用来表示工作线程的个数(workerCount)。
线程池源码实现:
3.2.2 核心线程数corePoolSize和最大线程数maximumPoolSize
线程多了浪费线程资源,少了又不能发挥线程池的性能,那到底该有多少个线程才合适呢?
- 核心线程数:corePoolSize 用来表示线程池中的核心线程的数量,也可以称为可闲置的线程数量
- 最大线程数:maximumPoolSize 用来表示线程池中最多能够创建的线程数量
创建线程是有代价的,不能每次要执行一个任务时就创建一个线程,但是也不能在任务非常多的时候,只有少量的线程在执行,这样任务是来不及处理的,而是应该创建合适的足够多的线程来及时的处理任务。随着任务数量的变化,当任务数明显很小时,原本创建的多余的线程就没有必要再存活着了,因为这时使用少量的线程就能够处理的过来了,所以说真正工作的线程的数量,是随着任务的变化而变化的。
工作线程的个数可能从0到最大线程数之间变化,当执行一段时间之后可能维持在 corePoolSize,但也不是绝对的,取决于核心线程是否允许被超时回收。
3.2.3 创建线程的工厂threadFactory
线程池源码实现:
3.2.4 缓存任务的阻塞队列BlockingQueue
当线程池接收到一个任务时,如果工作线程数没有达到corePoolSize,那么就会新建一个线程,并绑定该任务,直到工作线程的数量达到 corePoolSize 前都不会重用之前的线程。
当工作线程数达到 corePoolSize 了,这时又接收到新任务时,会将任务存放在一个阻塞队列中等待核心线程去执行。为什么不直接创建更多的线程来执行新任务呢,原因是核心线程中很可能已经有线程执行完自己的任务了,或者有其他线程马上就能处理完当前的任务,并且接下来就能投入到新的任务中去,所以阻塞队列是一种缓冲的机制,给核心线程一个机会让他们充分发挥自己的能力。另外一个值得考虑的原因是,创建线程毕竟是比较昂贵的,不可能一有任务要执行就去创建一个新的线程。
3.2.5 非核心线程存活时间keepAliveTime
3.2.6 拒绝策略RejectedExecutionHandler
3.3 工作流程
3.3.1 提交任务流程
对应execute()源码:
3.3.2 创建工作线程流程
3.3.3 启动工作线程流程
3.3.4 获取任务并执行流程
4.线程池内部是调用run()还是start()?
待验证:run()方法,如下:构造方法里,thread变量构造的线程是它本身this。
也就是说,最终执行的是Worker类的run方法。
3.四种线程池的区别
4.线程池任务队列为什么要保证线程安全?
6.单线程池只有一个线程,若当前线程有异常未处理,当前线程池是什么情况?
7.单线程池,什么情况下才会发生oom?
8.四种线程池什么情况下会发生oom?
9.threadFactory线程池工厂的作用?