Java线程模型学习笔记
线程
线程模型是什么
因为Java字节码运行在JVM(Java虚拟机)中,JVM运行在各个OS(操作系统)上,所以当JVM想要进行线程创建和回收的操作时,必须要调用OS的相关接口。
JVM线程与OS线程之间存在某种映射关系,这两种不同维度的线程之间的规范和协议,就是线程模型。
JVM线程对不同OS原生线程进行了高级抽象,使使用者不需要关注下层细节,只需专注上层开发。
线程和进程
- Linux线程(Kernel Level Thread,KLT):抽象概念
- Linux轻量级进程(Light Weight Process,LWP):具体实现
OS的内核线程:在linux中线程是抽象概念,linux内没有专门为线程定义数据结构和调度算法,所以linux实现线程的方式是轻量级进程(本质上还是进程),进程和轻量级进程的区别为:一个linux进程拥有自己独立的地址空间,而一个轻量级进程没有,只能共享同一个进程组下的地址空间。
进程和轻量级进程都是使用了clone系统调用,区别在于调用clone函数的参数不同,不同参数指定是否共享地址空间等资源。
OS两种CPU状态
- 内核态:运行操作系统程序,操作硬件
- 用户态:运行用户程序
OS指令划分
- 特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机
- 非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态)
特权级别
特权环:R0、R1、R2和R3
R0相当于内核态,R3相当于用户态;不同级别能够运行不同的指令集合;
内核态&用户态区别
- 处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的
- 处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。
内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。
当程序运行在0级特权级上时,就可以称之为运行在内核态。
运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。
三种线程模型
1:1(一对一)
在用户线程和内核线程之间建立了一对一的关系。目前大部分主流的JVM上都是采用一对一的下线程模型
缺点:
- 用户线程的阻塞和唤醒会直接映射到内核线程上,容易频繁引起用户态和内核态的切换,降低性能。但是一些语言引入CAS来避免一部分情况下的状态切换,比如Java引入了AQS这种函数级别的锁,减少使用内核级别的锁,就能够有效提升性能。
- Linux内核能够创建的线程数是有限的,在一定程度上会限制并发量。
N:1(多对一)
多个用户线程映射到一个内核线程上,用户线程的调度完全由用户空间来完成,调度都在用户空间内完成,可以有效提升性能。但有个致命缺陷是,当一个用户线程进行了内核调用并且阻塞,那其他线程在这段时间都无法进行内核调用。Java早期版本使用的是这种线程模型,但后来被抛弃。
M:N(多对多)
多对多模型可以有效解决一对一和多对一模型中的缺陷,但是实现这种线程模型的难度较高,目前go语言采用的GMP线程模型就是基于M:N的方式来实现。
Java线程调度
线程调度是指系统为线程分配处理使用权的过程。
调度方式
调度的主要方式有两种:
- 协同式
- 抢占式
Java使用抢占式线程调度,线程调度由自己完成,但可以通过设置线程优先级来实现,Java设置了10个级别的线程优先级。
线程状态转换
线程状态
新建 就绪 运行 阻塞 等待 死亡
线程状态转换
xxx
未完待续