天天看點

android方法調用擷取上下文_多線程的上下文切換

雙十一前的一個多月,所有的電商相關的系統都在進行壓測,不斷的優化系統,我們的電商ERP系統也進行了一個多月的壓測和優化的過程,在這其中,我們發現了大量的逾時報警,通過工具分析,我們發現是cs名額很高,然後分析日志,我們發現有大量wait()相關的Exception,這個時候我們懷疑是在多線程并發處理的時候,出現了大量的線程處理不及時導緻的這些問題,後來我們通過減小線程池最大線程數,再進行壓測發現系統的性能有了不小的提升。

我們都知道,在并發程式設計中,并不是線程越多就效率越高,線程數太少可能導緻資源不能充分利用,線程數太多可能導緻競争資源激烈,然後上下文切換頻繁造成系統的額外開銷。

什麼是上下文切換

我們都知道,在處理多線程并發任務的時候,處理器會給每個線程配置設定CPU時間片,線程在各自配置設定的時間片内執行任務,每個時間片的大小一般為幾十毫秒,是以在一秒鐘就可能發生幾十上百次的線程互相切換,給我們的感覺就是同時進行的。

線程隻在配置設定的時間片内占用處理器,當一個線程配置設定的時間片用完了,或者自身原因被迫暫停運作的時候,就會有另外一個線程來占用這個處理器,這種一個線程讓出處理器使用權,另外一個線程擷取處理器使用權的過程就叫做上下文切換。

一個線程讓出處理器使用權,就是“切出”;另外一個線程擷取處理器使用權。就是“切入”,在這個切入切出的過程中,作業系統會儲存和恢複相關的進度資訊,這個進度資訊就是我們常說的“上下文”,上下文中一般包含了寄存器的存儲内容以及程式計數器存儲的指令内容。

上下文切換的原因

多線程程式設計中,我們知道線程間的上下文切換會導緻性能問題,那麼是什麼原因造成的線程間的上下文切換。我們先看一下線程的生命周期,從中看一下找找答案。

android方法調用擷取上下文_多線程的上下文切換

線程的五種狀态我們都非常清楚: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程式調用導緻切出,一般是在編碼的時候,調用一下幾個方法或關鍵字:

sleep()wait()yield()join()park();synchronizedlock
           

非自發的上下文切換常見的有:線程被配置設定的時間片用完,虛拟機垃圾回收導緻,或者執行優先級的問題導緻。

小測試發現上下文切換

我們通過一個例子來看一下并發執行和串行執行的速度對比;

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

執行結果;

multi thread exce time: 5149scounter: 100000000serial exec time: 956scounter: 100000000
           

通過執行的結果對比我們可以看到,串行的執行速度比并發執行的速度更快,這其中就是因為多線程的上下文切換導緻了系統額外的開銷,使用的synchronized關鍵字,導緻了鎖競争,導緻了線程上下文切換,這個地方如果不使用synchronized關鍵字,并發的執行效率也比不上串行執行的速度,因為沒有鎖競争多線程的上下文切換依然存在。

系統開銷在上下文切換的哪些環節:

  • 作業系統儲存和恢複上下文
  • 處理器高速緩存加載
  • 排程器進行排程
  • 上下文切換可能導緻的高速緩沖區被沖刷

總結

上下文就是一個釋放處理器的使用權,另外一個線程擷取處理器的使用權,自發和非自發的調用操作,都會導緻上下文切換,會導緻系統資源開銷。線程越多不一定執行的速度越快,在單個邏輯比較簡單的時候,而且速度相對來說非常快的情況下,我們推薦是使用單線程。如果邏輯非常複雜,或者需要進行大量的計算的地方,我們建議使用多線程來提高系統的性能。

- END -