什么是线程、并发-J.U.C并发系列(2)

回顾

回顾上一篇的文章,咱们主要介绍了现代计算机模型,CPU的缓存一致性协议,CPU和内存的工做原理,这些知识点都是为了更好的去学习咱们的Java并发编程。java

介绍

本文,咱们来了解一个概念,什么是线程?
Java中线程和计算机的线程有什么区别?

什么是线程

现代操做系统在运行一个程序时,会为其建立一个进程。例如,启动一个Java程序、网页、软件应用等,操做系统就会建立一个进程。现代操做系统调度CPU的最小单元是线程,也叫轻量级进程(Light Weight Process),在一个进程里能够建立多个线程,这些线程都拥有各自的堆栈、局部变量、计数器等属性,而且可以访问共享的内存变量。处理器在这些线程上高速切换,让咱们感受到这些线程在同时并发执行。算法

进程是系统分配资源的基本单位,线程是调度CPU的基本单位,一个进程至少包含一个执行线程(main线程),线程依附在进程当中,每一个线程都有一组寄存器(保存当前线程的工做变量)、堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)、一个程序计数器(记录要执行的下一条操做指令)编程

CPU会提供一个时间片来执行线程的代码块,而后快速的切换不一样的线程,以达到并发的效果。缓存

须要知道,咱们的JVM中的Thread是没法直接操做CPU的,JVM是依赖的底层的操做系统,所以会带来一个概念,线程类型。安全

操做系统空间

  • 内核空间数据结构

    • 系统核心,底层的进程
  • 用户空间多线程

    • JVM
    • eclipse应用
    • 视频播放器

线程类型并发

  • 用户级线程(User-Level Thread)
  • 内核线线程(Kernel-Level Thread)

线程级别类型

CPU级别eclipse

Intel的CPU将特权级别分为4个级别:RING0,RING1,RING2,RING3。Windows只使用其中的两个级别RING0和RING3,RING0只给操做系统用,RING3谁都能用。若是普通应用程序企图执行RING0指令,则Windows会显示“非法指令”错误信息。在用户空间中,JVM会建立一个ULT级别线程,只能拥有Ring3级别权限。函数

而Ring0级别ULT是没法去调用操做,至于为何要这样划分?

出发是为了安全性考虑,假如ULT能够任意去操做CPU,拥有Ring0级别,那JVM的线程去肆意的攻击,修改其余的进程的指令,数据。会致使安全性问题。若是不限制,那内核里面的指令能够被修改,病毒能够随意的植入。

线程调度

JVM假如须要生成一个内核级线程的话,能够怎么操做?

能够经过调用内核空间提供的系统调用接口(JNI)去建立一个KLT级别线程。

建立了KLT级别线程以后,才能够去使用CPU,才会被分配时间片。

用户线程

指不须要内核支持而在用户程序中实现的线程,其不依赖于操做系统核心,应用进程利用线程库提供建立、同步、调度和管理线程的函数来控制用户线程。另外,用户线程是由应用进程利用线程库建立和管理,不依赖于操做系统核心。不须要用户态/核心态切换(上下文切换),速度快。操做系统内核不知道多线程的存在,所以一个线程阻塞将使得整个进程(包括它的全部线程)阻塞。因为这里的处理器时间片分配是以进程为基本单位,因此每一个线程执行的时间相对减小。

内核线程

线程的全部管理操做都是由操做系统内核完成的。内核保存线程的状态和上下文信息,当一个线程执行了引发阻塞的系统调用时,内核能够调度该进程的其余线程执行。在多处理器系统上,内核能够分派属于同一进程的多个线程在多个处理器上运行,提升进程执行的并行度。因为须要内核完成线程的建立、调度和管理,因此和用户级线程相比这些操做要慢得多,可是仍然比进程的建立和管理操做要快。大多数市场上的操做系统,如Windows,Linux等都支持内核级线程。

原理区分以下

咱们看KLT,每一个进程中的线程,所有依附于内核中,在内核中都会有对一个线程表一一对应,能够理解为轻量级的小进程,对应于具体的那个用户空间的线程的具体任务,同时也拥有Ring0级别的CPU特权。

Java中建立的是哪一个级别的线程?

  • 1.2时,建立的是ULT
  • 1.2以后,建立的是KLT
private native void start0();

Java线程与系统内核线程关系

JVM建立线程以后,会去经过库调度器调用,在内核空间中生成一个内核线程,并在内核空间的线程表关系中,进行一一映射对应。

Java建立线程

  1. new java.lang.Thread().start()
  2. 使用JNI将一个native thread attach到JVM中

针对 new java.lang.Thread().start()这种方式,只有调用start()方法的时候,才会真正的在

JVM中去建立线程,主要的生命周期步骤有

  1. 建立对应的JavaThread的instance
  2. 建立对应的OSThread的instance
  3. 建立实际的底层操做系统的native thread
  4. 准备相应的JVM状态,好比ThreadLocal存储空间分配等
  5. 底层的native thread开始运行,调用java.lang.Thread生成的Object的run()方法
  6. 当java.lang.Thread生成的Object的run()方法执行完毕返回后,或者抛出异常终止后,终止native thread
  7. 释放JVM相关的thread的资源,清除对应的JavaThread和OSThread

针对JNI将一个native thread attach到JVM中,主要的步骤有

  1. 经过JNI call AttachCurrentThread申请链接到执行的JVM实例
  2. JVM建立相应的JavaThread和OSThread对象
  3. 建立相应的java.lang.Thread的对象
  4. 一旦java.lang.Thread的Object建立以后,JNI就能够调用Java代码了
  5. 当经过JNI call DetachCurrentThread以后,JNI就从JVM实例中断开链接
  6. JVM清除相应的JavaThread, OSThread, java.lang.Thread对象

Java线程的生命周期

以下图所示

为何用到并发?并发会产生什么问题?

为何用到并发

并发编程的本质其实就是利用多线程技术,在现代多核的CPU的背景下,催生了并发编程的趋势,经过并发编程的形式能够将多核CPU的计算能力发挥到极致,性能获得提高。除此以外,面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。

即便是单核处理器也支持多线程执行代码,CPU经过给每一个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,由于时间片很是短,因此CPU经过不停地切换线程执行,让咱们感受多个线程是同时执行的,时间片通常是几十毫秒(ms)。

并发不等于并行:并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,若是系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能经过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出如今拥有多个CPU的系统中。

并发的优势

  1. 充分利用多核CPU的计算能力;
  2. 方便进行业务拆分,提高应用性能;

并发产生的问题

  • 高并发场景下,致使频繁的上下文切换
  • 临界区线程安全问题,容易出现死锁的,产生死锁就会形成系统功能不可用
  • 其它

CPU经过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。可是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,能够再加载这个任务的状态。因此任务从保存到再加载的过程就是一次上下文切换。

线程上下文切换过程:

上下文切换

image.png

Linux为内核代码和数据结构预留了几个页框,这些页永远不会被转出到磁盘上。从0x00000000 到 0xc0000000(PAGE_OFFSET) 的线性地址可由用户代码 和 内核代码进行引用(即用户空间)。从0xc0000000(PAGE_OFFSET)到 0xFFFFFFFFF的线性地址只能由
内核代码进行访问(即内核空间)。内核代码及其数据结构都必须位于这 1 GB的地址空间中,可是对于此地址空间而言,更大的消费者是物理地址的虚拟映射。

这意味着在 4 GB 的内存空间中,只有 3 GB 能够用于用户应用程序。一个进程只能运行在用户方式(usermode)或内核方式(kernelmode)下。用户程序运行在用户方式下,而系统调用运行在内核方式下。在这两种方式下所用的堆栈不同:用户方式下用的是通常的堆栈,而内核方式下用的是固定大小的堆栈(通常为一个内存页的大小)。

每一个进程都有本身的 3 G 用户空间,它们共享1GB的内核空间。当一个进程从用户空间进入内核空间时,它就再也不有本身的进程空间了。这也就是为何咱们常常说线程上下文切换会涉及到用户态到内核态的切换缘由所在。

以上图为例,来介绍下,CPU的上下文切换

第一步

线程A申请到了时间片A,执行相关的业务逻辑,当时间到达以后,CPU纸箱执行线程B的时间片B

这个时候线程A须要把一个临时中间状态进行存储,以便以后继续执行。

会把执行的结果经过CPU寄存器 ---> 缓存 -- >经过bus总线(缓存一致性协议)写回到主内存中。

中间的一些状态会存放到主内存中的内核空间,一个叫作Tss任务状态段的地方,存储了程序指令、程序指针、中间数据等。

第二步

执行时间片B,执行完以后继续指向线程A的时间片A。

这个时候CPU须要从新想内存中load上一个时间片执行的中间结果程序指令、程序指针、中间数据。

而后从新继续执行线程A的逻辑。

小结

本文介绍了什么是线程,并发,上下文切换的相关知识,但愿对你有所帮助。

相关文章
相关标签/搜索