多線程上下文切換

一、CPU時間片
  • CPU時間片即CPU分配給每個線程的執(zhí)行時間段,稱作它的時間片。CPU時間片一般為幾十毫秒(ms)。
二、什么是上下文切換

CPU通過時間片段的算法來循環(huán)執(zhí)行線程任務(wù),而循環(huán)執(zhí)行即每個線程允許運行的時間后的切換,而這種循環(huán)的切換使各個程序從表面上看是同時進行的。而切換時會保存之前的線程任務(wù)狀態(tài),當切換到該線程任務(wù)的時候,會重新加載該線程的任務(wù)狀態(tài)。而這個從保存到加載的過程稱之為上下文切換。

  • 若當前線程還在運行而時間片結(jié)束后,CPU將被剝奪并分配給另一個線程。
  • 若線程在時間片結(jié)束前阻塞或結(jié)束,CPU進行線程切換。而不會造成CPU資源浪費。
三、上下文切換造成的影響

我們可以通過對比串聯(lián)執(zhí)行和并發(fā)執(zhí)行進行對比。

    private static final long count = 1000000;

    public static void main(String[] args) throws Exception {
        concurrency();
        series();
    }
    /**
     * 并發(fā)執(zhí)行
     * @throws Exception
     */
    private static void concurrency() throws Exception {
        long start = System.currentTimeMillis();
        //創(chuàng)建線程執(zhí)行a+=
        Thread thread = new Thread(new Runnable() {
            public void run() {
                int a = 0;
                for (int i = 0; i < count; i++) {
                    a += 1;
                }
            }
        });
        //啟動線程執(zhí)行
        thread.start();
        //使用主線程執(zhí)行b--;
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        //合并線程,統(tǒng)計時間
        thread.join();
        long time = System.currentTimeMillis() - start;
        System.out.println("Concurrency:" + time + "ms, b = " + b);
    }
    /**
     * 串聯(lián)執(zhí)行
     */
    private static void series() {
        long start = System.currentTimeMillis();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a += 1;
        }
        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);
    }

通過修改循環(huán)次數(shù),對比串行運行和并發(fā)運行的時間測試結(jié)果:

循環(huán)次數(shù) 并發(fā)執(zhí)行時間 串聯(lián)執(zhí)行時間
一百萬 2ms 4ms
十萬 2ms 2ms
一萬 1ms 0ms

通過數(shù)據(jù)的對比我們可以看出。在一萬以下的循環(huán)次數(shù)時,串聯(lián)的執(zhí)行速度比并發(fā)的執(zhí)行速度塊。是因為線程上下文切換導致額外的開銷。

在Linux系統(tǒng)下可以使用vmstat命令來查看上下文切換的次數(shù),如果要查看上下文切換的時長,可以利用Lmbench3,這是一個性能分析工具。

四、如何減少上下文切換導致額外的開銷

減少上下文切換次數(shù)便可以提高多線程的運行效率。減少上下文切換的方法有無鎖并發(fā)編程、CAS算法、避免創(chuàng)建過多的線程和使用協(xié)程。

  • 無鎖并發(fā)編程.當任何特定的運算被阻塞的時候,所有CPU可以繼續(xù)處理其他的運算。換種方式說,在無鎖系統(tǒng)中,當給定線程被其他線程阻塞的時候,所有CPU可以不停的繼續(xù)處理其他工作。無鎖算法大大增加系統(tǒng)整體的吞吐量,因為它只偶爾會增加一定的交易延遲。大部分高端數(shù)據(jù)庫系統(tǒng)是基于無鎖算法而構(gòu)造的,以滿足不同級別。

  • CAS算法。Java提供了一套原子性操作的數(shù)據(jù)類型(java.util.concurrent.atomic包下),使用CAS算法來更新數(shù)據(jù),不需要加鎖。如:AtomicInteger、AtomicLong等。

  • 避免創(chuàng)建過多的線程。如任務(wù)量少時,盡可能減少創(chuàng)建線程。對于某個時間段任務(wù)量很大的這種情況,我們可以通過線程池來管理線程的數(shù)量,避免創(chuàng)建過多線程。

  • 協(xié)程:即協(xié)作式程序,其思想是,一系列互相依賴的協(xié)程間依次使用CPU,每次只有一個協(xié)程工作,而其他協(xié)程處于休眠狀態(tài)。如:JAVA中使用wait和notify來達到線程之間的協(xié)同工作。

參考:
《Java并發(fā)編程的藝術(shù)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容