目录

1 线程的实现

主流操作系统都提供线程的实现,在这基础上,上层应用可以构建自己的线程实现方式(Java、php、go的线程实现各不一样)。三种线程实现方式:内核线程实现(1:1实现),用户线程实现(1:N实现), 用户线程加轻量级进程混合实现(N:M实现)

1.1 内核线程实现

内核线程:直接由操作系统内核支持的线程:

角色说明:


【资料图】

工作流程:

  1. 应用程序通过轻量级进程接口,提交创建线程请求,连同任务内容传入内核
  2. 内核接收到线程创建请求,创建内核线程,同时创建面向应用程序的线程。线程与内核线程为1:1
  3. 内核控制线程调度器,将内核线程分派处理器,由处理器执行线程任务

应用:Java

1.2 用户线程实现

定义:完全建立在用户空间的线程库上,用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助

优点:不需要在内核态和用户态来回切换,因此快速且低消耗缺点:线程的创建、销毁、切换和调度等操作都由用户实现,复杂应用:Golang、Erlang等以高并发为卖点的编程语言,支持用户线程

1.3 用户线程加轻量级进程混合实现

定义:将内核线程与用户线程一起使用优点:综合了两者的优点应用:一些UNIX系列的操作系统,如Solaris、HP-UX

2 Java线程实现

采用:内核线程实现(1:1实现)

HotSpot:每个Java线程映射到一个操作系统原生线程来实现,虚拟机不会干涉线程调度(可以设置线程优先级给操作系统提供调度建议),全权交给操作系统去处理,包括:何时冻结或唤醒线程、该给线程分配多少cpu时间片、该把线程分给哪个处理器核心去执行。

注意:《Java虚拟机规范》没有限定线程采用什么模型来实现

3 Java线程调度

线程调度:系统为线程分配CPU使用权的过程,调度主要方式有两种,协同式线程调度抢占式线程调度

3.1 协同式线程调度

方式:执行时间由线程自身来控制,执行结束要主动通知系统切换到其它线程优点:简单、不会有同步问题缺点:一个线程有异常会导致系统停顿应用:Lua语言中的“协同例程”

3.2 抢占式线程调度

方式:线程将由系统来分配执行时间,线程的切换不由线程本身来决定。优点:不会因为一个线程异常导致系统停顿应用:Java语言。

3.3 Java线程优先级

Java多线程环境下:

  1. 允许设置线程优先级给OS提供调度建议:处于Ready状态的线程,优先级越高的越容易被执行。
  2. Thread::yield()方法可以主动让出执行时间
  3. Java线程不能主动抢占执行时间 【只能让出】

设置线程优先级非完全可靠

  1. OS可能会越过外部设置的优先级(Windows:“优先级推进器”:当系统发现一个线程被执行得特别频繁时,可能会越过线程优先级去为它分配执行时间)
  2. Java的线程优先级跟OS的线程优先级可能不匹配(Java10种,windows7种)

4 Java线程状态

六种状态:

线程状态转换关系:

5 为什么内核线程调度切换成本更高?

5.1 成本在哪里

内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要来自于响应中断、保护和恢复执行现场的成本

5.2 什么是上下文

5.3 线程切换的成本分析

假设发生了这样一次线程切换:

线程A -> 系统中断 -> 线程B

成本分析:

  1. 已知1:程序的运行是代码+数据的组合,数据保存在“上下文”中
  2. 已知2:各种存储设备、寄存器是被操作系统内所有线程共享的资源
  3. 当中断发生,从线程A切换到线程B去执行之前,操作系统首先要把线程A的上下文数据妥善保管好,然后把寄存器、内存分页等恢复到线程B挂起时候的状态,线程B被重新激活并继续执行
  4. 保护和恢复现场的过程,涉及一系列数据在各种寄存器、缓存中的来回拷贝,这边便是成本所在

6 Java线程模型面临的困境

已知:Java采用1:1的内核线程模型

以前:单体应用,处理一个请求允许花费很长时间在单体应用中,线程数量少,线程切换的成本低

当下:微服务,服务数量多,线程数量也变多

矛盾:每个请求本身的执行时间变得很短、数量变得很多的前提下,用户线程切换的开销甚至可能会接近用于计算本身的开销,这就会造成严重的浪费。

7 学习收获

Java程序面向用户线程,操作系统管理内核线程,内核线程和用户线程1:1对应关系
保护和恢复现场的过程,涉及一系列数据在各种寄存器、缓存中的来回拷贝

推荐内容