首页 > 其他分享 >多线程中的上下文切换

多线程中的上下文切换

时间:2023-06-07 20:44:21浏览次数:33  
标签:上下文 counter 线程 切换 多线程 public

我们都知道,在并发编程中,并不是线程越多就效率越高,线程数太少可能导致资源不能充分利用,线程数太多可能导致竞争资源激烈,然后上下文切换频繁造成系统的额外开销。大量的超时报警,通过工具分析,cs指标很高,然后分析日志,发现有大量wait()相关的Exception,这个时候我们怀疑是在多线程并发处理的时候,出现了大量的线程处理不及时导致的这些问题,后来我们通过减小线程池最大线程数,再进行压测发现系统的性能有了不小的提升。

什么是上下文切换

我们都知道,在处理多线程并发任务的时候,处理器会给每个线程分配CPU时间片,线程在各自分配的时间片内执行任务,每个时间片的大小一般为几十毫秒,所以在一秒钟就可能发生几十上百次的线程相互切换,给我们的感觉就是同时进行的。

线程只在分配的时间片内占用处理器,当一个线程分配的时间片用完了,或者自身原因被迫暂停运行的时候,就会有另外一个线程来占用这个处理器,这种一个线程让出处理器使用权,另外一个线程获取处理器使用权的过程就叫做上下文切换。

一个线程让出处理器使用权,就是“切出”;另外一个线程获取处理器使用权。就是“切入”,在这个切入切出的过程中,操作系统会保存和恢复相关的进度信息,这个进度信息就是我们常说的“上下文”,上下文中一般包含了寄存器的存储内容以及程序计数器存储的指令内容。

上下文切换的原因

多线程编程中,我们知道线程间的上下文切换会导致性能问题,那么是什么原因造成的线程间的上下文切换。我们先看一下线程的生命周期,从中看一下找找答案。

线程的五种状态我们都非常清楚:NEW、RUNNABLE、RUNNING、BLOCKED、DEAD,对应的Java中的六种状态分别为:NEW、RUNABLE、BLOCKED、WAINTING、TIMED_WAITING、TERMINADTED。

图中,一个线程从RUNNABLE到RUNNING的过程就是线程的上下文切换,RUNNING状态到BLOCKED、再到RUNNABLE、再从RUNNABLE到RUNNING的过程就是一个上下文切换的过程。一个线程从RUNNING转为BLOCKED状态时,我们叫做线程的暂停,线程暂停了,这个处理器就会有别的线程来占用,操作系统就会保存相应的上下文,为了这个线程以后再进入RUNNABLE状态时可以接着之前的执行进度继续执行。当线程从BLOCKED状态进入到RUNNABLE时,也就是线程的唤醒,此时线程将获取上次保存的上下文信息。

我们看到,多线程的上下文切换实际上就是多线程两个运行状态的相互切换导致的。

我们知道两种情况可以导致上下文切换:一种是程序本身触发的切换,这种我们一般称为自发性上下文切换,另一种是系统或者虚拟机导致的上下阿文切换,我们称之为非自发性上下文切换。

自发性上下文是线程由Java程序调用导致切出,一般是在编码的时候,调用一下几个方法或关键字:

  scss 复制代码
sleep()
wait()
yield()
join()
park();
synchronized
lock

非自发的上下文切换常见的有:线程被分配的时间片用完,虚拟机垃圾回收导致,或者执行优先级的问题导致。

小测试发现上下文切换

我们通过一个例子来看一下并发执行和串行执行的速度对比;

  java 复制代码

public class DemoApplication {
       public static void main(String[] args) {
              //运行多线程
              MultiThreadTester test1 = new MultiThreadTester();
              test1.Start();
              //运行单线程
              SerialTester test2 = new SerialTester();
              test2.Start();
       }
       
       
       static class MultiThreadTester extends ThreadContextSwitchTester {
              @Override
              public void Start() {
                     long start = System.currentTimeMillis();
                     MyRunnable myRunnable1 = new MyRunnable();
                     Thread[] threads = new Thread[4];
                     //创建多个线程
                     for (int i = 0; i < 4; i++) {
                           threads[i] = new Thread(myRunnable1);
                           threads[i].start();
                     }
                     for (int i = 0; i < 4; i++) {
                           try {
                                  //等待一起运行完
                                  threads[i].join();
                           } catch (InterruptedException e) {
                                  // TODO Auto-generated catch block
                                  e.printStackTrace();
                           }
                     }
                     long end = System.currentTimeMillis();
                     System.out.println("multi thread exce time: " + (end - start) + "s");
                     System.out.println("counter: " + counter);
              }
              // 创建一个实现Runnable的类
              class MyRunnable implements Runnable {
                     public void run() {
                           while (counter < 100000000) {
                                  synchronized (this) {
                                         if(counter < 100000000) {
                                                increaseCounter();
                                         }
                                         
                                  }
                           }
                     }
              }
       }
       
      //创建一个单线程
       static class SerialTester extends ThreadContextSwitchTester{
              @Override
              public void Start() {
                     long start = System.currentTimeMillis();
                     for (long i = 0; i < count; i++) {
                           increaseCounter();
                     }
                     long end = System.currentTimeMillis();
                     System.out.println("serial exec time: " + (end - start) + "s");
                     System.out.println("counter: " + counter);
              }
       }

       //父类
       static abstract class ThreadContextSwitchTester {
              public static final int count = 100000000;
              public volatile int counter = 0;
              public int getCount() {
                     return this.counter;
              }
              public void increaseCounter() {
                     
                     this.counter += 1;
              }
              public abstract void Start();
       }
}

执行结果;

  yaml 复制代码
multi thread exce time: 5149s
counter: 100000000
serial exec time: 956s
counter: 100000000

通过执行的结果对比我们可以看到,串行的执行速度比并发执行的速度更快,这其中就是因为多线程的上下文切换导致了系统额外的开销,使用的synchronized关键字,导致了锁竞争,导致了线程上下文切换,这个地方如果不使用synchronized关键字,并发的执行效率也比不上串行执行的速度,因为没有锁竞争多线程的上下文切换依然存在。

系统开销在上下文切换的哪些环节:

  • 操作系统保存和恢复上下文
  • 处理器高速缓存加载
  • 调度器进行调度
  • 上下文切换可能导致的高速缓冲区被冲刷

总结

上下文就是一个释放处理器的使用权,另外一个线程获取处理器的使用权,自发和非自发的调用操作,都会导致上下文切换,会导致系统资源开销。线程越多不一定执行的速度越快,在单个逻辑比较简单的时候,而且速度相对来说非常快的情况下,我们推荐是使用单线程。如果逻辑非常复杂,或者需要进行大量的计算的地方,我们建议使用多线程来提高系统的性能。

标签:上下文,counter,线程,切换,多线程,public
From: https://www.cnblogs.com/huigui-mint/p/17464498.html

相关文章

  • 关于Java中多线程
    基本概念什么是进程-->是操作系统资源分配和调度的最小(基本)单位(操作系统分配给当前进程一个内存区域供其使用)什么是线程-->是程序运行的基本单位(等待操作系统分配时间片让CPU执行该内存区域中的代码)进程和线程的关系-->一个进程可以存在多个线程线程是由进程创建的(寄......
  • Java多线程-工具篇-BlockingQueue
    前言:   在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景。认......
  • 多线程:线程的常用方法
             ......
  • 多线程:线程创建方式二(匿名内部类写法)
        ......
  • CS5290兼容CS5230防破音AB/D切换,5.2W单声道GF类音频功放IC
    CS5290E是一款采用CMOS工艺,电容式升压型GF类单声道音频功放,可以为4Q的负载提供最高5.2W的连续功率;CS5290E芯片内部固定的28倍增益,有效的减少了外围元器件的数量;功放集成了D类和AB类两种工作模式即可保证D类模式下强劲的功率输出,又可兼顾系统在有FM的情况下,消除功放对系统的干扰;CS52......
  • 第五节 5with管理文件操作上下文
    在Python中,进行文件操作时,需要打开文件、读写文件、关闭文件等过程。如果代码有错误或者忘记关闭文件就会导致程序出错或文件资源泄露问题。为了更方便、更安全地进行文件操作,Python提供了with语句来管理文件的操作上下文。使用with语句可以确保在任何情况下,文件都会被正确地关闭......
  • mmm手动切换IP,切换角色
    //mmm手动切换IP,切换角色//mmm切换IP[root@home-db4~]#mmm_controlset_passive [root@home-db4~]#mmm_controlset_ip10.200.3.119db1//mmm切换角色[root@home-db4~]#mmm_controlset_active[root@home-db4~]#mmm_controlmove_rolewriterdb1[root@home-db4~]......
  • 01_多线程
    多线程一、进程与线程1.1、进程:进程:是正在运行的程序是系统进行资源分配和调用的独立单位每个进程都有它自己的内存空间和系统资源 1.2、线程:在一个进程内部,可以执行一个任务,也可以执行多个任务线程:是进程中的单个执行顺序控制流,是一条执行路径单线程:一个进程......
  • uniapp主题切换功能的第一种实现方式(scss变量+vuex)
    随着用户端体验的不断提升,很多应用在上线的时候都要求做不同的主题,最基本的就是白天与夜间主题。就像b站app主题切换,像这样的uniapp因为能轻松实现多端发布而得到很多开发者的青睐,但每个端的实现也有可能不同,现我把已实现的功能一点点的大家分享给大家,须要的可以参考一下,可......
  • 在centos7升级nodejs存在的无法切换版本的问题解决
    1.安装n管理工具npminstall-gn安装最新版本nlatest安装指定版本 n8.11.3 2.切换nodejs版本n选择已安装的版本 ο node/8.11.3  node/10.4.1查看当前版本node-v,下面表示已切换成功v8.13.3但问题来了,切换后,查看版本还是原来的v6.13.3,看下面 使用n切换nodejs......