雙十一前的一個多月寺庄,所有的電商相關(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)課程推薦: