多線程中的上下文切換

雙十一前的一個多月寺庄,所有的電商相關(guān)的系統(tǒng)都在進(jìn)行壓測俐巴,不斷的優(yōu)化系統(tǒng),我們的電商ERP系統(tǒng)也進(jìn)行了一個多月的壓測和優(yōu)化的過程牵囤,在這其中爸黄,我們發(fā)現(xiàn)了大量的超時報警,通過工具分析揭鳞,我們發(fā)現(xiàn)是cs指標(biāo)很高炕贵,然后分析日志,我們發(fā)現(xiàn)有大量wait()相關(guān)的Exception野崇,這個時候我們懷疑是在多線程并發(fā)處理的時候称开,出現(xiàn)了大量的線程處理不及時導(dǎo)致的這些問題,后來我們通過減小線程池最大線程數(shù)乓梨,再進(jìn)行壓測發(fā)現(xiàn)系統(tǒng)的性能有了不小的提升鳖轰。

我們都知道,在并發(fā)編程中扶镀,并不是線程越多就效率越高蕴侣,線程數(shù)太少可能導(dǎo)致資源不能充分利用,線程數(shù)太多可能導(dǎo)致競爭資源激烈臭觉,然后上下文切換頻繁造成系統(tǒng)的額外開銷昆雀。

什么是上下文切換

我們都知道,在處理多線程并發(fā)任務(wù)的時候蝠筑,處理器會給每個線程分配CPU時間片狞膘,線程在各自分配的時間片內(nèi)執(zhí)行任務(wù),每個時間片的大小一般為幾十毫秒什乙,所以在一秒鐘就可能發(fā)生幾十上百次的線程相互切換挽封,給我們的感覺就是同時進(jìn)行的。

線程只在分配的時間片內(nèi)占用處理器稳强,當(dāng)一個線程分配的時間片用完了场仲,或者自身原因被迫暫停運行的時候,就會有另外一個線程來占用這個處理器退疫,這種一個線程讓出處理器使用權(quán)渠缕,另外一個線程獲取處理器使用權(quán)的過程就叫做上下文切換。

一個線程讓出處理器使用權(quán)褒繁,就是“切出”亦鳞;另外一個線程獲取處理器使用權(quán)。就是“切入”,在這個切入切出的過程中燕差,操作系統(tǒng)會保存和恢復(fù)相關(guān)的進(jìn)度信息遭笋,這個進(jìn)度信息就是我們常說的“上下文”,上下文中一般包含了寄存器的存儲內(nèi)容以及程序計數(shù)器存儲的指令內(nèi)容徒探。

上下文切換的原因

多線程編程中瓦呼,我們知道線程間的上下文切換會導(dǎo)致性能問題,那么是什么原因造成的線程間的上下文切換测暗。我們先看一下線程的生命周期央串,從中看一下找找答案。

線程的五種狀態(tài)我們都非常清楚:NEW碗啄、RUNNABLE质和、RUNNING、BLOCKED稚字、DEAD饲宿,對應(yīng)的Java中的六種狀態(tài)分別為:NEW、RUNABLE胆描、BLOCKED瘫想、WAINTING、TIMED_WAITING袄友、TERMINADTED殿托。

圖中霹菊,一個線程從RUNNABLE到RUNNING的過程就是線程的上下文切換剧蚣,RUNNING狀態(tài)到BLOCKED、再到RUNNABLE旋廷、再從RUNNABLE到RUNNING的過程就是一個上下文切換的過程鸠按。一個線程從RUNNING轉(zhuǎn)為BLOCKED狀態(tài)時,我們叫做線程的暫停饶碘,線程暫停了目尖,這個處理器就會有別的線程來占用,操作系統(tǒng)就會保存相應(yīng)的上下文扎运,為了這個線程以后再進(jìn)入RUNNABLE狀態(tài)時可以接著之前的執(zhí)行進(jìn)度繼續(xù)執(zhí)行瑟曲。當(dāng)線程從BLOCKED狀態(tài)進(jìn)入到RUNNABLE時,也就是線程的喚醒豪治,此時線程將獲取上次保存的上下文信息洞拨。

我們看到,多線程的上下文切換實際上就是多線程兩個運行狀態(tài)的相互切換導(dǎo)致的负拟。

我們知道兩種情況可以導(dǎo)致上下文切換:一種是程序本身觸發(fā)的切換烦衣,這種我們一般稱為自發(fā)性上下文切換,另一種是系統(tǒng)或者虛擬機導(dǎo)致的上下文切換,我們稱之為非自發(fā)性上下文切換花吟。

自發(fā)性上下文是線程由Java程序調(diào)用導(dǎo)致切出秸歧,一般是在編碼的時候,調(diào)用一下幾個方法或關(guān)鍵字:

sleep()wait()yield()join()park();synchronizedlock

非自發(fā)的上下文切換常見的有:線程被分配的時間片用完衅澈,虛擬機垃圾回收導(dǎo)致键菱,或者執(zhí)行優(yōu)先級的問題導(dǎo)致。

小測試發(fā)現(xiàn)上下文切換

我們通過一個例子來看一下并發(fā)執(zhí)行和串行執(zhí)行的速度對比;

public?class?DemoApplication?{???????public?static?void?main(String[]?args)?{??????????????//運行多線程??????????????MultiThreadTester?test1?=?new?MultiThreadTester();??????????????test1.Start();??????????????//運行單線程??????????????SerialTester?test2?=?new?SerialTester();??????????????test2.Start();???????}?????????????????????static?class?MultiThreadTester?extends?ThreadContextSwitchTester?{??????????????@Override??????????????public?void?Start()?{?????????????????????long?start?=?System.currentTimeMillis();?????????????????????MyRunnable?myRunnable1?=?new?MyRunnable();?????????????????????Thread[]?threads?=?new?Thread[4];?????????????????????//創(chuàng)建多個線程?????????????????????for?(int?i?=?0;?i?<?4;?i++)?{???????????????????????????threads[i]?=?new?Thread(myRunnable1);???????????????????????????threads[i].start();?????????????????????}?????????????????????for?(int?i?=?0;?i?<?4;?i++)?{???????????????????????????try?{??????????????????????????????????//等待一起運行完??????????????????????????????????threads[i].join();???????????????????????????}?catch?(InterruptedException?e)?{??????????????????????????????????//?TODO?Auto-generated?catch?block??????????????????????????????????e.printStackTrace();???????????????????????????}?????????????????????}?????????????????????long?end?=?System.currentTimeMillis();?????????????????????System.out.println("multi?thread?exce?time:?"?+?(end?-?start)?+?"s");?????????????????????System.out.println("counter:?"?+?counter);??????????????}??????????????//?創(chuàng)建一個實現(xiàn)Runnable的類??????????????class?MyRunnable?implements?Runnable?{?????????????????????public?void?run()?{???????????????????????????while?(counter?<?100000000)?{??????????????????????????????????synchronized?(this)?{?????????????????????????????????????????if(counter?<?100000000)?{????????????????????????????????????????????????increaseCounter();?????????????????????????????????????????}???????????????????????????????????????????????????????????????????????????}???????????????????????????}?????????????????????}??????????????}???????}?????????????//創(chuàng)建一個單線程???????static?class?SerialTester?extends?ThreadContextSwitchTester{??????????????@Override??????????????public?void?Start()?{?????????????????????long?start?=?System.currentTimeMillis();?????????????????????for?(long?i?=?0;?i?<?count;?i++)?{???????????????????????????increaseCounter();?????????????????????}?????????????????????long?end?=?System.currentTimeMillis();?????????????????????System.out.println("serial?exec?time:?"?+?(end?-?start)?+?"s");?????????????????????System.out.println("counter:?"?+?counter);??????????????}???????} ???????//父類???????static?abstract?class?ThreadContextSwitchTester?{??????????????public?static?final?int?count?=?100000000;??????????????public?volatile?int?counter?=?0;??????????????public?int?getCount()?{?????????????????????return?this.counter;??????????????}??????????????public?void?increaseCounter()?{??????????????????????????????????????????this.counter?+=?1;??????????????}??????????????public?abstract?void?Start();???????}}

執(zhí)行結(jié)果;

multi?thread?exce?time:?5149scounter:?100000000serial?exec?time:?956scounter:?100000000

通過執(zhí)行的結(jié)果對比我們可以看到今布,串行的執(zhí)行速度比并發(fā)執(zhí)行的速度更快纱耻,這其中就是因為多線程的上下文切換導(dǎo)致了系統(tǒng)額外的開銷,使用的synchronized關(guān)鍵字险耀,導(dǎo)致了鎖競爭弄喘,導(dǎo)致了線程上下文切換,這個地方如果不使用synchronized關(guān)鍵字甩牺,并發(fā)的執(zhí)行效率也比不上串行執(zhí)行的速度蘑志,因為沒有鎖競爭多線程的上下文切換依然存在。

系統(tǒng)開銷在上下文切換的哪些環(huán)節(jié):

操作系統(tǒng)保存和恢復(fù)上下文

處理器高速緩存加載

調(diào)度器進(jìn)行調(diào)度

上下文切換可能導(dǎo)致的高速緩沖區(qū)被沖刷

總結(jié)

上下文就是一個釋放處理器的使用權(quán)贬派,另外一個線程獲取處理器的使用權(quán)急但,自發(fā)和非自發(fā)的調(diào)用操作,都會導(dǎo)致上下文切換搞乏,會導(dǎo)致系統(tǒng)資源開銷波桩。線程越多不一定執(zhí)行的速度越快,在單個邏輯比較簡單的時候,而且速度相對來說非秤牢冢快的情況下权均,我們推薦是使用單線程。如果邏輯非常復(fù)雜萤皂,或者需要進(jìn)行大量的計算的地方,我們建議使用多線程來提高系統(tǒng)的性能匣椰。

- END -

文章來源:

面試 | 多線程中的上下文切換_故里學(xué)Java的博客-CSDN博客

相關(guān)課程推薦:

java零基礎(chǔ)入門到精通(2019版)

Android開發(fā)視頻教程

PHP入門教程_PHP入門視頻教程|黑馬程序員

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裆熙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子禽笑,更是在濱河造成了極大的恐慌入录,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佳镜,死亡現(xiàn)場離奇詭異僚稿,居然都是意外死亡,警方通過查閱死者的電腦和手機邀杏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門贫奠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唬血,“玉大人,你說我怎么就攤上這事唤崭】胶蓿” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵谢肾,是天一觀的道長腕侄。 經(jīng)常有香客問我,道長芦疏,這世上最難降的妖魔是什么冕杠? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮酸茴,結(jié)果婚禮上分预,老公的妹妹穿的比我還像新娘。我一直安慰自己薪捍,他們只是感情好笼痹,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酪穿,像睡著了一般凳干。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上被济,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天救赐,我揣著相機與錄音,去河邊找鬼只磷。 笑死经磅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喳瓣。 我是一名探鬼主播馋贤,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼畏陕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仿滔,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤惠毁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后崎页,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞠绰,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年飒焦,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜈膨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屿笼。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翁巍,靈堂內(nèi)的尸體忽然破棺而出驴一,到底是詐尸還是另有隱情,我是刑警寧澤灶壶,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布肝断,位于F島的核電站,受9級特大地震影響驰凛,放射性物質(zhì)發(fā)生泄漏胸懈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一恰响、第九天 我趴在偏房一處隱蔽的房頂上張望趣钱。 院中可真熱鬧,春花似錦胚宦、人聲如沸羔挡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绞灼。三九已至,卻和暖如春呈野,著一層夾襖步出監(jiān)牢的瞬間低矮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工被冒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留军掂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓昨悼,卻偏偏與公主長得像蝗锥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子率触,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容