共计 2858 个字符,预计需要花费 8 分钟才能阅读完成。
摘要
之前咱们解说了 cpu 多多级缓存模型,以及为什么须要引入 cpu 多级缓存模型?(为了解决 cpu 运算速度远高于基于 I / O 总线读取主内存数据速度)而后引入 cpu 多级缓存模型之后产生的问题?(数据缓存一致性)而后就是解决 cpu 缓存一致性问题的计划?(总线加锁及缓存一致性协定 MESI)而后具体解说了缓存一致性协定 MESI 中多线程读写主内存数据时候产生的问题;这一讲次要解说下什么是线程以及为什么须要并发?
思维导图
咱们依照以下思维逻辑导图解说线程及并发。
内容
1、线程及过程
过程: 零碎分配资源的根本单位;其就是咱们运行的一个应用程序;比方:JVM 过程、微信、QQ。
线程: 操作系统调度 cpu 的根本单位;是过程中的一个场景快照。
线程及过程关系:
1、过程是零碎分配资源的根本单位;线程是调度 cpu 的根本单位。过程是没有调用 cpu 的权力的,如果咱们的 jvm 是一个应用程序,它自身是没有调用 cpu 的权力的(调用 cpu 的权力是操作系统的性能),如果咱们的 jvm 可能不依赖于咱们的操作系统。间接能够操作 cpu 和显卡的话,那么这个 jvm 不就是一个操作系统了吗?那他还装置到操作系统干嘛呢?线程是调度 cpu 的根本单位,自身是不具备更多的资源,只具备本身须要的简略资源,所以说线程会共享过程外面的资源。
2、综上所叙述:线程是调度 cpu 的根本单位,一个过程至多蕴含一个线程,线程寄生在过程当中。每个线程都有一个程序计数器(记录要执行的下一条指令)、一组寄存器(保留以后线程的工作变量)。
分类:
依照线程所属空间,咱们将线程分为:用户线程(User-Level Thread)、内核线程(Kernel-Level Thread);不同品种的线程工作在不同的空间外面。
咱们看下如下代码:
str =“I like learning“// 用户空间
x = x + 2
file.write(str) // 切换到内核空间
y=x+4 // 切换到用户空间
如上图:咱们的用户空间划分为两局部:用户空间、内核空间。
内核空间:零碎内核运行的空间。
用户空间:用户程序运行的空间;比方 JVM、PS、播放器。
咱们程序运行的状态,如果运行在内核空间就是内核态,运行在用户空间的话它是用户态。为了安全性起见,两者是隔离的,即便用户空间的 JVM 解体了,内核空间不受影响的。
内核空间的话是能够执行任何命令,调用零碎任何资源。用户空间的话简略运算,不能调度系统资源。内核空间会提供一个接口供用户空间发送指令。
cpu 特权级别: cpu 特权级别分为:ring0、ring3。
cpu 特权级别:为什么咱们的 JVM 须要依赖咱们的内核能力调用咱们的 cpu 呢?因为咱们的 cpu 分为了两级特权。一级是 ring0(最好级别,领有 cpu 最高操作权限)、一级是 ring3 最低级别(领有简略的操作权限, 没有外围操作权限); 用户空间只有 cpu 的 ring3 特权级别。内核空间只有 cpu 的 ring0 特权级别。为什么须要这样的划分?为了安全性:如果说咱们的 JVM 在用户空间,具备 cpu0 的特权,那么他就可能操作咱们的内核空间,这样的话通过更改内核能够植入病毒,以及管制其余应用程序。所以只有内核空间才具备操作 cpu 的最高特权级别。
用户线程与内核线程:
用户线程 (ULT): 就是在用户空间外面的过程创立的线程。用户线程它是没有 cpu 的应用权限的,它是不可能间接去调度 cpu(只能简略操作权限),因为咱们的内核是不晓得多线程的存在的。内核基本不晓得多线程的用户线程存在,因为在其外部保护的是一个 过程表 。 咱们 cpu 处理器资源的调配工夫分片的话,是以过程为根本单位的,所以线程是依靠于咱们的主过程去执行的。所有的线程都执行在同一条线上。这就有一个问题当咱们的用户线程阻塞了,比方线程 1 阻塞了。整个主过程将会被阻塞。如下:
内核线程(KLT): 内核线程由在内核空间创立,保护在内核空间的 线程表 外面(如下面图示: 过程表里保护了用户空间的过程、线程表外面保护了内核空间的线程);同理,内核过程也是在内核空间创立,在内核空间保护过程表。内核级线程是操作系统去实现的。cpu 会为咱们的内核级线程调配工夫片。所以多个线程都能够去抢夺 cpu 资源。内核线程阻塞了的话不会影响内核过程的运行。
在 java 外面用的是哪种线程呢?
在 java 外面 1.2 版本之前用的是 ULT;1.2 之后用的是 KLT 内核级线程;java 线程模型就是依赖于底层内核线程去保护的,两者有什么关系呢?如下:
他们是一一映射的关系。
如上:咱们的 jvm 过程是能够创立多个线程的,实质上是 jvm 去创立了 线程栈空间(其实没有去创立真正的线程);线程栈空间外面会有一些栈针指令;创立真正的线程是须要通过咱们的库调度器去调度咱们的内核空间去创立内核线程,从而操作调度 cpu;
java 线程 <—> 内核线程。
2、并发
Java 线程生命周期
Java 线程生命周期的话是仅仅限度在咱们的 JVM 外面,总共就只有 6 中状态:
新建:NEW
运行:RUNNABLE
期待:WAITING
阻塞:BLOCKED
终止:TERMINATED
留神,咱们的阻塞之后 不是不运行了,而是进入就绪状态,等待时间片调配。
为什么用并发
1、充分利用多核 CPU 的计算能力。
2、不便进行业务拆分,晋升利用性能。
并发产生的问题:
1、高并发场景下,导致频繁的上下文切换
2、临界区线程平安问题,容易呈现死锁的,产生死锁就会造成零碎性能不可用。
留神:在咱们单核处理器也反对多线程代码的,只不过是 cpu 给每一个线程调配工夫片(时分复用)来实现这种机制的。这情状况下 cpu 给每个线程调配的工夫比拟短,让咱们感觉多个线程如同是同时执行的。因为这个工夫片的工夫只有几十毫秒,是十分的短,然而须要频繁的线程上下文切换。
还有一点要须要留神的:并发与并行区别:
并发:多个工作交替执行;比方工夫片 cpu 的切分。
并行:才是真正意义上的多个工作同时进行。
如果咱们的零碎内核只有一个 cpu,那么他应用多线程时候,实际上并不是并行的,他只能通过工夫片的模式。去并发执行工作。真正意义上的并发只会呈现在多个 cpu 的零碎当中。
毛病:
如果在大并发条件下大量创立线程。因为咱们的 linux 零碎或者 jvm 他的线程数是有一个峰值的。如果超过了这个峰值的话,性能会大幅度降落。
线程上下文切换原理分析如下:
下面所示,线程并发执行的时候个别都是通过时分复用;线程抢夺 cpu 的工夫片来执行程序的。如果线程 T1 在工夫片 A 时候抢夺到了 cpu 的权力,而后到工夫片 B 时候,T1 还没有执行结束,然而此时工夫片 B 曾经被线程 T2 抢夺到了,所以线程线程 T1 的指令、程序指针、两头数据之前是存在与 cpu 的寄存器跟缓存外面的,这个时候就须要通过总线将线程 T1 寄存器和缓存里上下文内容(那个的指令,程序指针、两头数据)保留到内核空间的主内存外面去。保留到内核栈空间的 TSS 工作状态段外面去。而后当咱们的线程 T1 又争取到工夫片 c 时候;须要从内核空间主内存外面加载上下文数据到 cpu 的寄存器跟缓存外面去。大量线程上下文切换造成性能降落,并且可能产生死锁。