原创:享学课堂讲师Peter
转载请声明出处!
什么是上下文切换?
其实在单个处理器的时期,操作系统就能处理多线程并发任务。处理器给每个线程分配 CPU 时间片(Time Slice),线程在分配获得的时间片内执行任务。
CPU 时间片是 CPU 分配给每个线程执行的时间段,一般为几十毫秒。在这么短的时间内线程互相切换,我们根本感觉不到,所以看上去就好像是同时进行的一样。
时间片决定了一个线程可以连续占用处理器运行的时长。当一个线程的时间片用完了,或者因自身原因被迫暂停运行了,这个时候,另外一个线程(可以是同一个线程或者其它进程的线程)就会被操作系统选中,来占用处理器。这种一个线程被暂停剥夺使用权,另外一个线程被选中开始或者继续运行的过程就叫做上下文切换(Context Switch)。
多线程上下文切换的原因
上下文切换带来的性能问题
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();
}
}
执行之后,看一下两者的时间测试结果:
通过数据对比我们可以看到:串联的执行速度比并发的执行速度要快。这就是因为线程的上下文切换导致了额外的开销,一般来说使用 Synchronized 锁关键字,导致了资源竞争,从而引起了上下文切换,但即使不使用 Synchronized 锁关键字,并发的执行速度也无法超越串联的执行速度,这是 因为多线程同样存在着上下文切换。Redis、NodeJS 的设计就很好地体现了单线程串行的优势。
总结
例如,我们前面讲到的 Redis,从内存中快速读取值,不用考虑 I/O 瓶颈带来的阻塞问题。而在逻辑相对来说很复杂的场景,等待时间相对较长又或者是需要大量计算的场景,我建议使用多线程来提高系统的整体性能。例如,NIO 时期的文件读写操作、图像处理以及大数据分析等。
最后
如果你的技术提升遇到瓶颈了,或者缺高级进阶视频学习提升自己,这有大量大厂面试题为你面试做准备!
后台私信回复:“资料”获取。
喜欢本文的话可以关注我们的官方账号,第一时间获取资讯。
你的关注是对我们更新最大的动力哦~