對(duì)于上下文切換不同的操作系統(tǒng)模式也不盡相同,這里我們只討論Unix系統(tǒng),在我之前的文章中提到過windows的搶占式,這里就不在贅述。
無論是單核還是多核CPU都是支持多線程代碼的,CPU通過給每個(gè)線程分配CPU時(shí)間片來實(shí)這個(gè)機(jī)制。
時(shí)間片是CPU分配給各個(gè)線程的時(shí)間,因?yàn)闀r(shí)間片非常短,所以CPU通過不停地切換線程執(zhí)行,讓我們感覺多個(gè)線程是同時(shí)執(zhí)行的,時(shí)間片一般是幾十毫秒(ms)
CPU通過時(shí)間片分配算法來循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后會(huì)切換到下一個(gè)任務(wù)。但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài),以便下次切換回這個(gè)任務(wù)時(shí),可以再加載這個(gè)任務(wù)的狀態(tài)。
所以任務(wù)從保存到再加載的過程就是一次上下文切換。 很明顯上下文切換會(huì)影響多線程的執(zhí)行速度。
如何減少上下文切換
減少上下文切換的方法有
1、無鎖并發(fā)編程。
多線程競(jìng)爭(zhēng)鎖時(shí),會(huì)引起上下文切換,所以多線程處理數(shù)據(jù)時(shí),可以用一
些辦法來避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。
2、CAS算法。
Java的Atomic包使用CAS(compare and swap)算法來更新數(shù)據(jù),而不需要加鎖。
3、使用最少線程。避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多線程來處理,這
樣會(huì)造成大量線程都處于等待狀態(tài)。
4、協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換。
減少上下文切換的例子
下面我們看一個(gè)通過減少線上大量WAITING的線程,來減少上下文切換次數(shù)的例子:
使用jstack命令dump線程信息,看看pid為3117的進(jìn)程里的線程都在做什么
sudo -u admin /opt/java/bin/jstack 31177 > /home/java/dump17
統(tǒng)計(jì)所有線程分別處于什么狀態(tài),發(fā)現(xiàn)300多個(gè)線程處于WAITING(onobjectmonitor)狀態(tài)
1
2
3
4
5
6
7
8
|
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)的線程在做什么。發(fā)現(xiàn)這些線程基本全是JBOSS的工作線程,在await。說明JBOSS線程池里線程接收到的任務(wù)太少,大量線程都閑著。
1
2
3
4
5
6
7
8
9
10
|
"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的工作線程數(shù),找到JBOSS的線程池配置信息,將maxThreads降到100
1
2
3
4
5
|
<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線程信息,然后統(tǒng)計(jì)WAITING(onobjectmonitor)的線程,發(fā)現(xiàn)減少了175個(gè)。
WAITING的線程少了,系統(tǒng)上下文切換的次數(shù)就會(huì)少,因?yàn)槊恳淮螐腤AITTING到RUNNABLE都會(huì)進(jìn)行一次上下文的切換。
讀者也可以使用vmstat命令測(cè)試一下。
1
2
3
4
5
6
7
8
|
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) |
為什么要減少上下文切換
當(dāng)CPU從執(zhí)行一個(gè)線程切換到執(zhí)行另外一個(gè)線程的時(shí)候,它需要先存儲(chǔ)當(dāng)前線程的本地的數(shù)據(jù),程序指針等,然后載入另一個(gè)線程的本地?cái)?shù)據(jù),程序指針等,最后才開始執(zhí)行。
這種切換稱為“上下文切換”(“context switch”)。
CPU會(huì)在一個(gè)上下文中執(zhí)行一個(gè)線程,然后切換到另外一個(gè)上下文中執(zhí)行另外一個(gè)線程。上下文切換并不廉價(jià),是比較耗時(shí)的
以上這篇Java實(shí)現(xiàn)多線程的上下文切換就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://blog.csdn.net/fuyuwei2015/article/details/71860349