1. 程式人生 > >java基礎學習總結(二十):多執行緒上下文切換

java基礎學習總結(二十):多執行緒上下文切換

什麼是上下文切換

       即使是單核CPU也支援多執行緒執行程式碼,CPU通過給每個執行緒分配CPU時間片來實現這個機制。時間片是CPU分配給各個執行緒的時間,因為時間片非常短,所以CPU通過不停地切換執行緒執行,讓我們感覺多個執行緒時同時執行的,時間片一般是幾十毫秒(ms)。

          CPU通過時間片分配演算法來迴圈執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任務的狀態,以便下次切換回這個任務時,可以再次載入這個任務的狀態,從任務儲存到再載入的過程就是一次上下文切換

     這就像我們同時讀兩本書,當我們在讀一本英文的技術書籍時,發現某個單詞不認識,於是便開啟中英文詞典,但是在放下英文書籍之前,大腦必須先記住這本書讀到了多少頁的第多少行,等查完單詞之後,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多執行緒的執行速度。

上下文切換程式碼測試

下面的程式碼演示序列和併發執行並累加操作的時間:

public class ContextSwitchTest
{
    private static final long count = 10000;
    
    public static void main(String[] args) throws Exception
    {
        concurrency();
        serial();
    }
    
    private static void concurrency() throws Exception
    {
        long start = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable(){
            public void run()
            {
                int a = 0;
                for (int i = 0; i < count; i++)
                {
                    a += 5;
                }
            }
        });
        thread.start();
        int b = 0;
        for (long i = 0; i < count; i++)
        {
            b --;
        }
        thread.join();
        long time = System.currentTimeMillis() - start;
        System.out.println("Concurrency:" + time + "ms, b = " + b);
    }
    
    private static void serial()
    {
        long start = System.currentTimeMillis();
        int a = 0;
        for (long i = 0; i < count; i++)
        {
            a += 5;
        }
        int b = 0;
        for (int i = 0; i < count; i++)
        {
            b --;
        }
        long time = System.currentTimeMillis() - start;
        System.out.println("Serial:" + time + "ms, b = " + b + ", a = " + a);
    }
}

修改上面的count值,即修改迴圈次數,看一下序列執行和併發執行的時間測試結果:

     從表中可以看出,100次併發執行累加以下,序列執行和併發執行的執行速度總體而言差不多,1萬次以下序列執行甚至還可以說是略快。為什麼併發執行的速度會比序列慢呢?這就是因為執行緒有建立和上下文切換的開銷

引起執行緒上下文切換的原因

對於我們經常使用的搶佔式作業系統而言,引起執行緒上下文切換的原因大概有以下幾種:

  1. 當前執行任務的時間片用完之後,系統CPU正常排程下一個任務
  2. 當前執行任務碰到IO阻塞,排程器將此任務掛起,繼續下一任務
  3. 多個任務搶佔鎖資源,當前任務沒有搶到鎖資源,被排程器掛起,繼續下一任務
  4. 使用者程式碼掛起當前任務,讓出CPU時間
  5. 硬體中斷

如何減少上下文切換

既然上下文切換會導致額外的開銷,因此減少上下文切換次數便可以提高多執行緒程式的執行效率。減少上下文切換的方法有無鎖併發程式設計、CAS演算法、使用最少執行緒和使用協程

  • 無鎖併發程式設計。多執行緒競爭時,會引起上下文切換,所以多執行緒處理資料時,可以用一些辦法來避免使用鎖,如將資料的ID按照Hash取模分段,不同的執行緒處理不同段的資料
  • CAS演算法。Java的Atomic包使用CAS演算法來更新資料,而不需要加鎖
  • 使用最少執行緒。避免建立不需要的執行緒,比如任務很少,但是建立了很多執行緒來處理,這樣會造成大量執行緒都處於等待狀態
  • 協程。在單執行緒裡實現多工的排程,並在單執行緒裡維持多個任務間的切換