【漫画】揭秘上下文切换

原创:享学课堂讲师Peter
转载请声明出处!

什么是上下文切换?

【漫画】揭秘上下文切换_第1张图片
【漫画】揭秘上下文切换_第2张图片
【漫画】揭秘上下文切换_第3张图片
【漫画】揭秘上下文切换_第4张图片
【漫画】揭秘上下文切换_第5张图片
【漫画】揭秘上下文切换_第6张图片

其实在单个处理器的时期,操作系统就能处理多线程并发任务。处理器给每个线程分配 CPU 时间片(Time Slice),线程在分配获得的时间片内执行任务。

CPU 时间片是 CPU 分配给每个线程执行的时间段,一般为几十毫秒。在这么短的时间内线程互相切换,我们根本感觉不到,所以看上去就好像是同时进行的一样。

时间片决定了一个线程可以连续占用处理器运行的时长。当一个线程的时间片用完了,或者因自身原因被迫暂停运行了,这个时候,另外一个线程(可以是同一个线程或者其它进程的线程)就会被操作系统选中,来占用处理器。这种一个线程被暂停剥夺使用权,另外一个线程被选中开始或者继续运行的过程就叫做上下文切换(Context Switch)。

多线程上下文切换的原因

【漫画】揭秘上下文切换_第7张图片
【漫画】揭秘上下文切换_第8张图片
【漫画】揭秘上下文切换_第9张图片
【漫画】揭秘上下文切换_第10张图片

上下文切换带来的性能问题

【漫画】揭秘上下文切换_第11张图片
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){
 e.printStackTrace();
      }
   } 
 long end = System.currentTimeMillis(); 
 System.out.println("多线程运行时间: " + (end - start) + "ms"); 
 System.out.println("计数: " + 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("单线程运行时间: " + (end - start) + "ms");
 System.out.println("计数: " + counter); 
   }
}
 //父类 static abstract c

 lass 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(); 
    }
 }

执行之后,看一下两者的时间测试结果:

【漫画】揭秘上下文切换_第12张图片

通过数据对比我们可以看到:串联的执行速度比并发的执行速度要快。这就是因为线程的上下文切换导致了额外的开销,一般来说使用 Synchronized 锁关键字,导致了资源竞争,从而引起了上下文切换,但即使不使用 Synchronized 锁关键字,并发的执行速度也无法超越串联的执行速度,这是因为多线程同样存在着上下文切换。Redis、NodeJS 的设计就很好地体现了单线程串行的优势。

总结

【漫画】揭秘上下文切换_第13张图片

例如,我们前面讲到的 Redis,从内存中快速读取值,不用考虑 I/O 瓶颈带来的阻塞问题。而在逻辑相对来说很复杂的场景,等待时间相对较长又或者是需要大量计算的场景,我建议使用多线程来提高系统的整体性能。例如,NIO 时期的文件读写操作、图像处理以及大数据分析等。

【漫画】揭秘上下文切换_第14张图片

喜欢本文的话可以关注我们的官方账号,第一时间获取资讯。
你的关注是对我们更新最大的动力哦~

你可能感兴趣的:(【漫画】揭秘上下文切换)