天天看點

《Java并發程式設計的藝術》一一1.1 上下文切換

本節書摘來華章計算機出版社《java并發程式設計的藝術》一書中的第1章,第1.1節,作者:方騰飛 魏鵬 程曉明 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

即使是單核處理器也支援多線程執行代碼,cpu通過給每個線程配置設定cpu時間片來實作這個機制。時間片是cpu配置設定給各個線程的時間,因為時間片非常短,是以cpu通過不停地切換線程執行,讓我們感覺多個線程是同時執行的,時間片一般是幾十毫秒(ms)。

cpu通過時間片配置設定算法來循環執行任務,目前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任務的狀态,以便下次切換回這個任務時,可以再加載這個任務的狀态。是以任務從儲存到再加載的過程就是一次上下文切換。

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

1.1.1 多線程一定快嗎

下面的代碼示範串行和并發執行并累加操作的時間,請分析:下面的代碼并發執行一定比串行執行快嗎?

上述問題的答案是“不一定”,測試結果如表1-1所示。

《Java并發程式設計的藝術》一一1.1 上下文切換

從表1-1可以發現,當并發執行累加操作不超過百萬次時,速度會比串行執行累加操作要慢。那麼,為什麼并發執行的速度會比串行慢呢?這是因為線程有建立和上下文切換的

開銷。

1.1.2 測試上下文切換次數和時長

下面我們來看看有什麼工具可以度量上下文切換帶來的消耗。

使用lmbench3可以測量上下文切換的時長。

使用vmstat可以測量上下文切換的次數。

下面是利用vmstat測量上下文切換次數的示例。

cs(content switch)表示上下文切換的次數,從上面的測試結果中我們可以看到,上下文每1秒切換1000多次。

1.1.3 如何減少上下文切換

減少上下文切換的方法有無鎖并發程式設計、cas算法、使用最少線程和使用協程。

無鎖并發程式設計。多線程競争鎖時,會引起上下文切換,是以多線程處理資料時,可以用一些辦法來避免使用鎖,如将資料的id按照hash算法取模分段,不同的線程處理不同段的資料。

cas算法。java的atomic包使用cas算法來更新資料,而不需要加鎖。

使用最少線程。避免建立不需要的線程,比如任務很少,但是建立了很多線程來處理,這樣會造成大量線程都處于等待狀态。

協程:在單線程裡實作多任務的排程,并在單線程裡維持多個任務間的切換。

1.1.4 減少上下文切換實戰

本節将通過減少線上大量waiting的線程,來減少上下文切換次數。

第一步:用jstack指令dump線程資訊,看看pid為3117的程序裡的線程都在做什麼。

sudo -u admin /opt/ifeve/java/bin/jstack 31177 > /home/tengfei.fangtf/dump17

第二步:統計所有線程分别處于什麼狀态,發現300多個線程處于waiting(onobject-monitor)狀态。

第三步:打開dump檔案檢視處于waiting(onobjectmonitor)的線程在做什麼。發現這些線程基本全是jboss的工作線程,在await。說明jboss線程池裡線程接收到的任務太少,大量線程都閑着。

第四步:減少jboss的工作線程數,找到jboss的線程池配置資訊,将maxthreads降到100。

第五步:重新開機jboss,再dump線程資訊,然後統計waiting(onobjectmonitor)的線程,發現減少了175個。waiting的線程少了,系統上下文切換的次數就會少,因為每一次從waitting到runnable都會進行一次上下文的切換。讀者也可以使用vmstat指令測試一下。