日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java 优化线程_Java | 多线程调优(下):如何优化多线程上下文切换?

發(fā)布時間:2025/3/15 java 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 优化线程_Java | 多线程调优(下):如何优化多线程上下文切换? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

通過上一講的講解,相信你對上下文切換已經(jīng)有了一定的了解了。如果是單個線程,在 CPU 調(diào)用之后,那么它基本上是不會被調(diào)度出去的。如果可運行的線程數(shù)遠大于 CPU 數(shù)量,那么操作系統(tǒng)最終會將某個正在運行的線程調(diào)度出來,從而使其它線程能夠使用 CPU ,這就會導致上下文切換。

還有,在多線程中如果使用了競爭鎖,當線程由于等待競爭鎖而被阻塞時,JVM 通常會將這個鎖掛起,并允許它被交換出去。如果頻繁地發(fā)生阻塞,CPU 密集型的程序就會發(fā)生更多的上下文切換。

那么問題來了,我們知道在某些場景下使用多線程是非常必要的,但多線程編程給系統(tǒng)帶來了上下文切換,從而增加的性能開銷也是實打?qū)嵈嬖诘摹D敲次覀冊撊绾蝺?yōu)化多線程上下文切換呢?這就是我今天要和你分享的話題,我將重點介紹幾種常見的優(yōu)化方法。

競爭鎖優(yōu)化

大多數(shù)人在多線程編程中碰到性能問題,第一反應多是想到了鎖。

多線程對鎖資源的競爭會引起上下文切換,還有鎖競爭導致的線程阻塞越多,上下文切換就越頻繁,系統(tǒng)的性能開銷也就越大。由此可見,在多線程編程中,鎖其實不是性能開銷的根源,競爭鎖才是。

第 11~13 講中我曾集中講過鎖優(yōu)化,我們知道鎖的優(yōu)化歸根結(jié)底就是減少競爭。這講中我們就再來總結(jié)下鎖優(yōu)化的一些方式。

1. 減少鎖的持有時間

我們知道,鎖的持有時間越長,就意味著有越多的線程在等待該競爭資源釋放。如果是 Synchronized 同步鎖資源,就不僅是帶來線程間的上下文切換,還有可能會增加進程間的上下文切換。

在第 12 講中,我曾分享過一些更具體的方法,例如,可以將一些與鎖無關(guān)的代碼移出同步代碼塊,尤其是那些開銷較大的操作以及可能被阻塞的操作。

優(yōu)化前

public?synchronized?void?mySyncMethod(){

businesscode1();

mutextMethod();

businesscode2();

}

優(yōu)化后

public?void?mySyncMethod(){

businesscode1();

synchronized(this)

{

mutextMethod();

}

businesscode2();

}

2. 降低鎖的粒度

同步鎖可以保證對象的原子性,我們可以考慮將鎖粒度拆分得更小一些,以此避免所有線程對一個鎖資源的競爭過于激烈。具體方式有以下兩種:

與傳統(tǒng)鎖不同的是,讀寫鎖實現(xiàn)了鎖分離,也就是說讀寫鎖是由“讀鎖”和“寫鎖”兩個鎖實現(xiàn)的,其規(guī)則是可以共享讀,但只有一個寫。

這樣做的好處是,在多線程讀的時候,讀讀是不互斥的,讀寫是互斥的,寫寫是互斥的。而傳統(tǒng)的獨占鎖在沒有區(qū)分讀寫鎖的時候,讀寫操作一般是:讀讀互斥、讀寫互斥、寫寫互斥。所以在讀遠大于寫的多線程場景中,鎖分離避免了在高并發(fā)讀情況下的資源競爭,從而避免了上下文切換。

我們在使用鎖來保證集合或者大對象原子性時,可以考慮將鎖對象進一步分解。例如,我之前講過的 Java1.8 之前版本的 ConcurrentHashMap 就使用了鎖分段。

3. 非阻塞樂觀鎖替代競爭鎖

volatile 關(guān)鍵字的作用是保障可見性及有序性,volatile 的讀寫操作不會導致上下文切換,因此開銷比較小。但是,volatile 不能保證操作變量的原子性,因為沒有鎖的排他性。

而 CAS 是一個原子的 if-then-act 操作,CAS 是一個無鎖算法實現(xiàn),保障了對一個共享變量讀寫操作的一致性。CAS 操作中有 3 個操作數(shù),內(nèi)存值 V、舊的預期值 A 和要修改的新值 B,當且僅當 A 和 V 相同時,將 V 修改為 B,否則什么都不做,CAS 算法將不會導致上下文切換。Java 的 Atomic 包就使用了 CAS 算法來更新數(shù)據(jù),就不需要額外加鎖。

上面我們了解了如何從編碼層面去優(yōu)化競爭鎖,那么除此之外,JVM 內(nèi)部其實也對 Synchronized 同步鎖做了優(yōu)化,我在 12 講中有詳細地講解過,這里簡單回顧一下。

在 JDK1.6 中,JVM 將 Synchronized 同步鎖分為了偏向鎖、輕量級鎖、自旋鎖以及重量級鎖,優(yōu)化路徑也是按照以上順序進行。JIT 編譯器在動態(tài)編譯同步塊的時候,也會通過鎖消除、鎖粗化的方式來優(yōu)化該同步鎖。

wait/notify 優(yōu)化

在 Java 中,我們可以通過配合調(diào)用 Object 對象的 wait() 方法和 notify() 方法或 notifyAll() 方法來實現(xiàn)線程間的通信。

在線程中調(diào)用 wait() 方法,將阻塞等待其它線程的通知(其它線程調(diào)用 notify() 方法或 notifyAll() 方法),在線程中調(diào)用 notify() 方法或 notifyAll() 方法,將通知其它線程從 wait() 方法處返回。

下面我們通過 wait() / notify() 來實現(xiàn)一個簡單的生產(chǎn)者和消費者的案例,代碼如下:

public?class?WaitNotifyTest?{

public?static?void?main(String[]?args)?{

Vectorpool=new?Vector();

Producer?producer=new?Producer(pool,?10);

Consumer?consumer=new?Consumer(pool);

new?Thread(producer).start();

new?Thread(consumer).start();

}

}

/**

*?生產(chǎn)者

*?@author?admin

*

*/

class?Producer?implements?Runnable{

private?Vectorpool;

private?Integer?size;

public?Producer(Vectorpool,?Integer?size)?{

this.pool?=?pool;

this.size?=?size;

}

public?void?run()?{

for(;;){

try?{

System.out.println("生產(chǎn)一個商品?");

produce(1);

}?catch?(InterruptedException?e)?{

//?TODO?Auto-generated?catch?block

e.printStackTrace();

}

}

}

private?void?produce(int?i)?throws?InterruptedException{

while(pool.size()==size){

synchronized?(pool)?{

System.out.println("生產(chǎn)者等待消費者消費商品,當前商品數(shù)量為"+pool.size());

pool.wait();//等待消費者消費

}

}

synchronized?(pool)?{

pool.add(i);

pool.notifyAll();//生產(chǎn)成功,通知消費者消費

}

}

}

/**

*?消費者

*?@author?admin

*

*/

class?Consumer?implements?Runnable{

private?Vectorpool;

public?Consumer(Vectorpool)?{

this.pool?=?pool;

}

public?void?run()?{

for(;;){

try?{

System.out.println("消費一個商品");

consume();

}?catch?(InterruptedException?e)?{

//?TODO?Auto-generated?catch?block

e.printStackTrace();

}

}

}

private?void?consume()?throws?InterruptedException{

synchronized?(pool)?{

while(pool.isEmpty())?{

System.out.println("消費者等待生產(chǎn)者生產(chǎn)商品,當前商品數(shù)量為"+pool.size());

pool.wait();//等待生產(chǎn)者生產(chǎn)商品

}

}

synchronized?(pool)?{

pool.remove(0);

pool.notifyAll();//通知生產(chǎn)者生產(chǎn)商品

}

}

}

wait/notify 的使用導致了較多的上下文切換

結(jié)合以下圖片,我們可以看到,在消費者第一次申請到鎖之前,發(fā)現(xiàn)沒有商品消費,此時會執(zhí)行 Object.wait() 方法,這里會導致線程掛起,進入阻塞狀態(tài),這里為一次上下文切換。

當生產(chǎn)者獲取到鎖并執(zhí)行 notifyAll() 之后,會喚醒處于阻塞狀態(tài)的消費者線程,此時這里又發(fā)生了一次上下文切換。

被喚醒的等待線程在繼續(xù)運行時,需要再次申請相應對象的內(nèi)部鎖,此時等待線程可能需要和其它新來的活躍線程爭用內(nèi)部鎖,這也可能會導致上下文切換。

如果有多個消費者線程同時被阻塞,用 notifyAll() 方法,將會喚醒所有阻塞的線程。而某些商品依然沒有庫存,過早地喚醒這些沒有庫存的商品的消費線程,可能會導致線程再次進入阻塞狀態(tài),從而引起不必要的上下文切換。

優(yōu)化 wait/notify 的使用,減少上下文切換

首先,我們在多個不同消費場景中,可以使用 Object.notify() 替代 Object.notifyAll()。因為 Object.notify() 只會喚醒指定線程,不會過早地喚醒其它未滿足需求的阻塞線程,所以可以減少相應的上下文切換。

其次,在生產(chǎn)者執(zhí)行完 Object.notify() / notifyAll() 喚醒其它線程之后,應該盡快地釋放內(nèi)部鎖,以避免其它線程在喚醒之后長時間地持有鎖處理業(yè)務操作,這樣可以避免被喚醒的線程再次申請相應內(nèi)部鎖的時候等待鎖的釋放。

最后,為了避免長時間等待,我們常會使用 Object.wait (long)設(shè)置等待超時時間,但線程無法區(qū)分其返回是由于等待超時還是被通知線程喚醒,從而導致線程再次嘗試獲取鎖操作,增加了上下文切換。

這里我建議使用 Lock 鎖結(jié)合 Condition 接口替代 Synchronized 內(nèi)部鎖中的 wait / notify,實現(xiàn)等待/通知。這樣做不僅可以解決上述的 Object.wait(long) 無法區(qū)分的問題,還可以解決線程被過早喚醒的問題。

Condition 接口定義的 await 方法 、signal 方法和 signalAll 方法分別相當于 Object.wait()、 Object.notify() 和 Object.notifyAll()。

合理地設(shè)置線程池大小,避免創(chuàng)建過多線程

線程池的線程數(shù)量設(shè)置不宜過大,因為一旦線程池的工作線程總數(shù)超過系統(tǒng)所擁有的處理器數(shù)量,就會導致過多的上下文切換。更多關(guān)于如何合理設(shè)置線程池數(shù)量的內(nèi)容,我將在后續(xù)詳解。

還有一種情況就是,在有些創(chuàng)建線程池的方法里,線程數(shù)量設(shè)置不會直接暴露給我們。比如,用 Executors.newCachedThreadPool() 創(chuàng)建的線程池,該線程池會復用其內(nèi)部空閑的線程來處理新提交的任務,如果沒有,再創(chuàng)建新的線程(不受 MAX_VALUE 限制),這樣的線程池如果碰到大量且耗時長的任務場景,就會創(chuàng)建非常多的工作線程,從而導致頻繁的上下文切換。因此,這類線程池就只適合處理大量且耗時短的非阻塞任務。

使用協(xié)程實現(xiàn)非阻塞等待

相信很多人一聽到協(xié)程(Coroutines),馬上想到的就是 Go 語言。協(xié)程對于大部分 Java 程序員來說可能還有點陌生,但其在 Go 中的使用相對來說已經(jīng)很成熟了。

協(xié)程是一種比線程更加輕量級的東西,相比于由操作系統(tǒng)內(nèi)核來管理的進程和線程,協(xié)程則完全由程序本身所控制,也就是在用戶態(tài)執(zhí)行。協(xié)程避免了像線程切換那樣產(chǎn)生的上下文切換,在性能方面得到了很大的提升。協(xié)程在多線程業(yè)務上的運用,我會在后續(xù)文章中詳述。

減少 Java 虛擬機的垃圾回收

我們在上一講講上下文切換的誘因時,曾提到過“垃圾回收會導致上下文切換”。

很多 JVM 垃圾回收器(serial 收集器、ParNew 收集器)在回收舊對象時,會產(chǎn)生內(nèi)存碎片,從而需要進行內(nèi)存整理,在這個過程中就需要移動存活的對象。而移動內(nèi)存對象就意味著這些對象所在的內(nèi)存地址會發(fā)生變化,因此在移動對象前需要暫停線程,在移動完成后需要再次喚醒該線程。因此減少 JVM 垃圾回收的頻率可以有效地減少上下文切換。

總結(jié)

上下文切換是多線程編程性能消耗的原因之一,而競爭鎖、線程間的通信以及過多地創(chuàng)建線程等多線程編程操作,都會給系統(tǒng)帶來上下文切換。除此之外,I/O 阻塞以及 JVM 的垃圾回收也會增加上下文切換。

總的來說,過于頻繁的上下文切換會影響系統(tǒng)的性能,所以我們應該避免它。另外,我們還可以將上下文切換也作為系統(tǒng)的性能參考指標,并將該指標納入到服務性能監(jiān)控,防患于未然。

總結(jié)

以上是生活随笔為你收集整理的java 优化线程_Java | 多线程调优(下):如何优化多线程上下文切换?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 精品无码国产av一区二区三区 | 91久久伊人| 欧美一区二区三区视频在线 | 综合网在线视频 | 精品国产xxx | 91av国产精品 | 国产精品电影院 | 亚洲国产精品免费 | 91尤物在线 | 国产免费91视频 | 一区二区www| 欧美破处女 | 污视频网站在线观看 | 亚洲精品推荐 | 韩日中文字幕 | 国产毛片儿 | 国产三级在线看 | 黄色a区 | 超碰97国产在线 | 国产亚洲精品久久久久丝瓜 | 毛片av在线播放 | 亚洲欧美一区二区视频 | 成人观看网站 | 骚虎tv| 欧洲精品免费一区二区三区 | 五月婷婷丁香网 | 亚洲精选久久久 | 特级毛片www | 国产欧美一区二区精品性色超碰 | 亚洲精品乱码久久久久久不卡 | 最新黄色网址在线观看 | 日日摸日日操 | 久久亚洲精精品中文字幕早川悠里 | 欧美一级片免费在线观看 | av一区二区三区在线观看 | 精品国产乱码一区二区三区99 | 奇米色777 | 狠狠老司机 | 韩国不卡av | 92av视频| 国产麻豆成人精品av | 亚洲天天操 | a v视频在线观看 | 日韩黄色影视 | 久久综合久久综合久久综合 | 国产成人精品亚洲 | 国产99久久精品 | 超碰毛片 | av一区二区免费 | 秋霞av在线 | 国产又粗又猛又黄 | 台湾佬美性中文娱乐 | 深夜福利视频在线 | 黄色不卡视频 | 亚洲日日日 | 91在线日本 | 中文字幕av一区二区三区谷原希美 | www.插插插 | 亚洲免费观看在线 | 国产乱妇无码大片在线观看 | 二区三区偷拍浴室洗澡视频 | 97免费在线观看视频 | 99无码熟妇丰满人妻啪啪 | 九九视频在线观看 | 99久久伊人 | 五月天堂网 | 日本一本在线视频 | 日本天堂在线视频 | 在线免费观看你懂的 | 国产精品亚洲lv粉色 | 在线观看视频日韩 | 特黄一级片| 黄色激情视频网站 | 中文字幕第九页 | 日韩美女少妇 | 亚洲性影院 | 毛片a级片| 精品一区二区三区蜜桃 | 欧美黄色一区二区 | 性淫影院| 在线天堂www在线国语对白 | a久久久久久| 蜜臀久久精品久久久久久酒店 | 国产69精品久久久 | 动漫美女隐私无遮挡 | 无码人妻精品一区二 | 欧美成人精品一区二区男人小说 | 999精品在线 | jizzjizz日本免费视频 | 久久重口味 | 欧美少妇xx | 亚洲色图丝袜 | 国产精品成人av性教育 | 午夜精品网站 | 久久永久免费 | 久久久久久综合网 | 亚洲精品乱码久久久久久写真 | 欧美日韩国产伦理 | 极品探花在线观看 |