线程、线程池剖析

 

目录

 

1. run()和start()区别

2. 为什么start()一个线程只能调用一次?

3.线程池是怎样工作的?

4.线程池内部是调用run()还是start()?


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如下: 

线程第二次调用start

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线程池工厂的作用?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值