原創:享學課堂講師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 時期的文件讀寫操作、圖像處理以及大數據分析等。
喜歡本文的話可以關注我們的官方賬號,第一時間獲取資訊。
你的關注是對我們更新最大的動力哦~