1. 程式人生 > >Java多執行緒的上下文切換

Java多執行緒的上下文切換

轉載自 https://blog.csdn.net/fuyuwei2015/article/details/71860349

對於上下文切換不同的作業系統模式也不盡相同,這裡我們只討論Unix系統,在我之前的文章中提到過windows的搶佔式,這裡就不在贅述。 
無論是單核還是多核CPU都是支援多執行緒程式碼的,CPU通過給每個執行緒分配CPU時間片來實這個機制。時間片是CPU分配給各個執行緒的時間,因為時間片非常短,所以CPU通過不停地切換執行緒執行,讓我們感覺多個執行緒是同時執行的,時間片一般是幾十毫秒(ms) 
CPU通過時間片分配演算法來迴圈執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任務的狀態,以便下次切換回這個任務時,可以再載入這個任務的狀態。所以任務從儲存到再載入的過程就是一次上下文切換。 很明顯上下文切換會影響多執行緒的執行速度。

如何減少上線文切換

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

減少上下文切換的例子

下面我們看一個通過減少線上大量WAITING的執行緒,來減少上下文切換次數的例子: 
使用jstack命令dump執行緒資訊,看看pid為3117的程序裡的執行緒都在做什麼

sudo -u admin /opt/java/bin/jstack 31177 > /home/java/dump17

統計所有執行緒分別處於什麼狀態,發現300多個執行緒處於WAITING(onobjectmonitor)狀態

grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
| sort | uniq -c
39 RUNNABLE
21 TIMED_WAITING(onobjectmonitor)
6 TIMED_WAITING(parking)
51 TIMED_WAITING(sleeping)
305 WAITING(onobjectmonitor)
3 WAITING(parking)

開啟dump檔案檢視處於WAITING(onobjectmonitor)的執行緒在做什麼。發現這些執行緒基本全是JBOSS的工作執行緒,在await。說明JBOSS執行緒池裡執行緒接收到的任務太少,大量執行緒都閒著。

"http-0.0.0.0-7001-97" daemon prio=10 tid=0x000000004f6a8000 nid=0x555e in
Object.wait() [0x0000000052423000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
at java.lang.Object.wait(Object.java:485)
at org.apache.tomcat.util.net.AprEndpoint$Worker.await(AprEndpoint.java:1464)
- locked <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1489)
at java.lang.Thread.run(Thread.java:662)

減少JBOSS的工作執行緒數,找到JBOSS的執行緒池配置資訊,將maxThreads降到100

<maxThreads="250" maxHttpHeaderSize="8192"
emptySessionPath="false" minSpareThreads="40" maxSpareThreads="75"
maxPostSize="512000" protocol="HTTP/1.1"
enableLookups="false" redirectPort="8443" acceptCount="200" bufferSize="16384"
connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI= "true">

重啟JBOSS,再dump執行緒資訊,然後統計WAITING(onobjectmonitor)的執行緒,發現減少了175個。WAITING的執行緒少了,系統上下文切換的次數就會少,因為每一次從WAITTING到RUNNABLE都會進行一次上下文的切換。讀者也可以使用vmstat命令測試一下。

grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
| sort | uniq -c
44 RUNNABLE
22 TIMED_WAITING(onobjectmonitor)
9 TIMED_WAITING(parking)
36 TIMED_WAITING(sleeping)
130 WAITING(onobjectmonitor)
1 WAITING(parking)

為什麼要減少上下文切換

當CPU從執行一個執行緒切換到執行另外一個執行緒的時候,它需要先儲存當前執行緒的本地的資料,程式指標等,然後載入另一個執行緒的本地資料,程式指標等,最後才開始執行。這種切換稱為“上下文切換”(“context switch”)。CPU會在一個上下文中執行一個執行緒,然後切換到另外一個上下文中執行另外一個執行緒。上下文切換並不廉價,是比較耗時的