关于CPU线程调度与并发、JAVA重量级锁的一些理解

  2020-5-3 


一个程序对应一个进程,一个进程下有很多线程

现代操作系统多采用时间片轮转的线程调度算法来管理线程并发任务,对于单颗CPU来说,它为每一个线程分配时间片,每个线程运行一个时间片长度就切换到另一个线程,像流水线一样。时间片切换的时候会发生线程的上下文切换,然而在CPU流水中的线程上下文切换并不怎么耗时间,因为切换都处在内核态中

时间片轮转算法让所有进程好像在“同时”执行,这就是并发;同一时刻执行才行并行(多核就能同一时刻执行)

回到java中,java程序中的每一个线程是与刚刚CPU线程调度的线程是一个东西,时间片轮转调度就调度的它。

时间片轮转调度实际上就是一个所有线程都公平竞争,排队各自轮流执行一小段时间。但是我们在java并发编程的时候,会遇到很多实际问题,比如线程间存在依赖关系等等,不能让它们公平竞争,这时候我们想到加锁来管理多线程并发,这是建立在CPU线程调度管理之上的人为干预。

锁有几种,偏向锁、轻量级锁实际上并没有对由操作系统管理的CPU线程调度造成改变,它们依然是时间片调度公平执行,只是实现了锁的效果

而重量级锁是获取了对象关联的monitor对象(每一个JAVA对象都会与一个监视器monitor关联,没有获取到monitor对象即竞争锁失败,阻塞),而monitor实际上是操作系统的高级原语,充当着维护 mutex(操作系统提供的互斥量)以及定义 wait/signal API 来管理线程的阻塞和唤醒的角色。这时候进行线程切换,就是通过系统来进行的,需要从用户态切换到核心态,然后再进行上下文切换。(概括:重量级锁的线程切换需要从用户态切换到核心态)而从用户态切换到核心态才是重量级锁耗时的元凶!

当程序中有系统调用语句,程序执行到系统调用时,首先使用类似int 80H的软中断指令,保存现场,去的系统调用号,在内核态执行,然后恢复现场,每个进程都会有两个栈,一个内核态栈和一个用户态栈。当执行int中断执行时就会由用户态,栈转向内核栈。系统调用时需要进行栈的切换。而且内核代码对用户不信任,需要进行额外的检查。系统调用的返回过程有很多额外工作,比如检查是否需要调度等。

为什么用户态和内核态的切换耗费时间?

(实际上使用mutex进行切换就是操作系统线程调度级别的线程切换了,它可能是强行将CPU时间片交给获得锁的线程,而无法获得锁的互斥线程就无法获得CPU时间片)


且听风吟