线程的上下文切换

上下文切换

1.对于CPU而言,在一个时刻只能运因自身原因被迫暂停运行,此时另一个线程会被操作系统选中来占用处理器行一个线程,当一个线程的时间片用完,或者

2.当CPU结束运行一个线程,转去执行另外一个线程,这个过程就叫做线程上下文切换

上下文

1.在发生切换的时候,当前线程的任务可能并没有执行完毕。所以在切换时需要保存线程切换前的运行状态,以便下一次,可以接着切换之前的状态继续执行后续的任务

2.切出切入的过程中,操作系统需要保存和恢复相应的进度信息,这个进度信息就是上下文

比如一个线程A正在读取一个文件的内容,正读到文件的一半,线程A的时间片结束,此时需要暂停线程A,CPU转去执行线程;当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取

多线程的上下文切换-影响-如何优化

 

上下文的内容

线程上下文切换过程中会涉及程序计数器(PCB,位于内存之中)、CPU寄存器∶

  1. 寄存器的存储内容∶CPU寄存器负责存储已经、正在和将要执行的任务
  2. 程序计数器存储的指令内容∶程序计数器负责存储CPU正在执行的指令位置、即将执行的下一条指令的位置

CPU寄存器

在线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少(也就是直接读取中间的结果,而不是从头开始读);中间结果会放在CPU的寄存器中

程序计数器

  1. 线程在进行切换时候,需要知道在这之前当前线程已经执行到哪条指令了,这些指令信息需要依靠程序计数器来保存
  2. 程序计数器是一块较小的内存空间,它保存了当前线程下一条需要执行的字节码指令的位置
  3. 每条线程都需要有一个独立的程序计数器,线程之间的程序计数器互不影响,寄存器和CPU是不同的

多线程的上下文切换-影响-如何优化

带来的系统开销

Java的线程是映射到操作系统原生线程之上的,如果要阻塞唤醒一个线程就需要操作系统介入,需要在用户态与内核态之间切换,这种切换会消耗大量的系统资源;比如运行的QQ和微信等都是运行在用户态中的

用户态-内核态

  • 在执行用户自己的代码时,称其处于用户态此时处理器特权级最低,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。(比如QQ,微信等等)
  • 当因为系统调用陷入内核代码中执行时(阻塞和唤醒),处于内核态,此时处理器处于特权级最高
  • 如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,此时需要从用户态切换到内核态。这些系统调用会调用内核的代码,在执行完后又会切换回用户态
  • 用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值变量等,以便内核态调用结束后切换回用户态继续工作。

 

多线程的上下文切换-影响-如何优化

上下文切换的两种方式:自发性上下文切换和非自发性地上下文切换

自发性上下文切换:主动进入阻塞状态

  • sleep  
  • wait ;等待一些情形的发生和等待锁
  • yield :主动地释放自己的CPU资源
  • join等待其他线程完成,然后再继续自己的任务
  • park
  • synchronized  
  • lock

非自发性上下文切换:被动进入阻塞状态

  • 线程被分配的时间片用完
  • JVM垃圾回收(STW Stop the world:full GC、线程暂停)
  • 线程执行优先级
  • 如何优化?-从减少线程上下文切换的角度出发

减少锁的竞争

  1.  多线程对锁资源的竞争,如果失败由于进入阻塞状态,将会引起上下文切换(锁本身不是带来性能开销的本质原因,锁竞争才是)
  2. 锁竞争导致的线程阻塞越多,上下文切换就越频繁,系统的性能开销就越大
  3. 在多线程编程中,锁本身不是性能开销的根源,锁竞争才是性能开销的根源
  4.  锁优化归根到底是减少竞争
  • 减少锁的持有时间

  • 锁的持有时间越长,意味着越多的线程会被阻塞,在等待该竞争锁释放
  • 优化方法∶将一些与锁无关的代码移出同步代码块,尤其是那些开销较大的操作以及可能被阻塞的操作
  • 减少锁的粒度-锁分离

  • 读写锁实现了锁分离,由读锁和写锁两个锁实现,可以共享读,但只有一个写
  • 读写锁在多线程读写时,读读不互斥,读写互斥,写写互斥
  • 传统的独占锁在多线程读写时,读读互斥,读写互斥,写写互斥
  • 在读远大于写的多线程场景中,锁分离避免了高并发读情况下的资源竞争,从而避免了上下文切换
  • 减少锁的粒度-锁分段

  • 在使用锁来保证集合或者大对象的原子性时,可以将锁对象进一步分解
  • Java 1.8之前的ConcurrentHashMap就是用了锁分段

相关文章:

  • 2022-12-23
  • 2021-11-27
  • 2021-07-14
  • 2022-12-23
  • 2021-07-02
猜你喜欢
  • 2021-12-06
  • 2021-08-28
  • 2021-05-22
  • 2021-09-25
  • 2021-08-06
相关资源
相似解决方案