天天看點

Java多線程的上下文切換如何減少上線文切換減少上下文切換的例子為什麼要減少上下文切換

對于上下文切換不同的作業系統模式也不盡相同,這裡我們隻讨論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  > /home/java/dump17
           

統計所有線程分别處于什麼狀态,發現300多個線程處于WAITING(onobjectmonitor)狀态

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

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

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

減少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
 RUNNABLE
 TIMED_WAITING(onobjectmonitor)
 TIMED_WAITING(parking)
 TIMED_WAITING(sleeping)
 WAITING(onobjectmonitor)
 WAITING(parking)
           

為什麼要減少上下文切換

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