偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

多線程優(yōu)化血虧教訓!這坑 99% 的人都踩過

開發(fā) 前端
多線程優(yōu)化就像走鋼絲,看著簡單,其實處處都是陷阱。咱得把基礎打扎實,多在實踐中總結經驗,遇到問題別慌,用調試工具和性能分析工具慢慢排查。希望大家看完這篇文章,能避開這些坑,在多線程優(yōu)化的路上少走彎路,寫出高效、穩(wěn)定的代碼。

兄弟們,今天咱來嘮嘮多線程優(yōu)化這事兒。說起來都是淚啊,當年我在項目里搞多線程優(yōu)化,那叫一個自信滿滿,覺得自己吃透了Java并發(fā)編程,結果硬生生踩了一堆坑,差點被領導拎去祭天。咱今天就把這些血虧教訓掰開了揉碎了,讓大家少走彎路。

一、線程池參數(shù)拍腦袋設置?坑你沒商量

剛入行那會,聽說線程池能提高效率,嘿,那必須用?。∩蟻砭褪莕ewFixedThreadPool(100),心想100個線程同時干活,這效率不得起飛?結果上線沒兩天,服務器直接卡死,GC日志跟下雨似的嘩嘩往外冒。

1. 問題出在哪?

咱先看看FixedThreadPool的源碼,它用的是無界隊列LinkedBlockingQueue。我設置了100個核心線程,想著處理100個任務夠了吧?可現(xiàn)實是任務源源不斷過來,全往隊列里塞,隊列無限增長,內存直接爆掉。就好比你開了個餐廳,雇了100個服務員(核心線程),結果來了1000個客人,你讓他們全在大廳等著(無界隊列),大廳擠爆了也不管,最后只能關門大吉。

2. 正確姿勢是啥?

咱得根據任務類型來設置參數(shù)。要是CPU密集型任務,線程數(shù)一般設為CPU核心數(shù)+1,為啥加1呢?因為線程切換也需要時間,多一個線程可以在某個線程阻塞時頂上。要是IO密集型任務,那就可以多設點,比如CPU核心數(shù)*2。而且隊列最好用有界隊列,比如ArrayBlockingQueue,防止內存溢出。還有拒絕策略,不能默認用AbortPolicy,直接拋異常,咱可以用CallerRunsPolicy,讓調用者線程來處理任務,給系統(tǒng)一個緩沖機會。

我后來在一個文件處理系統(tǒng)里優(yōu)化線程池,根據服務器是8核CPU,任務是IO密集型,設置核心線程數(shù)為16,最大線程數(shù)32,隊列大小200,拒絕策略用CallerRunsPolicy,結果處理速度提升了3倍,內存也穩(wěn)定了。

二、鎖濫用:以為加鎖就安全,性能直接撲街

在處理共享資源時,大家都知道要加鎖,可我之前就犯了個傻,在一個高頻調用的方法里,不管三七二十一,直接給整個方法加了synchronized鎖。想著這下安全了,結果性能測試的時候,吞吐量直接腰斬,線程競爭特別激烈。

1. 鎖的粒度沒控制好

整個方法加鎖,相當于把整個房間都鎖起來,每次只能一個人進去,其他人都得在外面等著。其實咱可以縮小鎖的范圍,只對共享資源加鎖。比如有個訂單處理類,里面有個共享的訂單號生成變量,我之前給整個處理訂單的方法加鎖,后來改成只在生成訂單號的代碼塊上加鎖,性能立馬提升了50%。

2. 鎖的類型沒選對

synchronized是獨占鎖,競爭激烈時效率不高。咱可以用ReentrantLock,它支持公平鎖和非公平鎖,還能嘗試獲取鎖。比如在一個高并發(fā)的庫存扣減場景,用ReentrantLock的tryLock方法,避免線程長時間阻塞。還有讀寫鎖ReadWriteLock,讀多寫少的場景用它,讀的時候可以多個線程同時讀,寫的時候才加鎖,效率杠杠的。

我之前在一個緩存系統(tǒng)里,用synchronized來控制對緩存的讀寫,結果讀操作都被阻塞了。換成ReadWriteLock后,讀操作的吞吐量提升了80%,寫操作雖然稍微慢了點,但整體性能大幅提升。

三、盲目追求高并發(fā):線程越多越好?圖樣圖森破

那時候總覺得線程越多,并行度越高,性能就越好。于是在一個任務處理系統(tǒng)里,開了200個線程去處理任務,結果任務處理速度不僅沒提升,反而下降了,CPU利用率倒是100%,但系統(tǒng)就是卡得不行。

1. 上下文切換惹的禍

CPU核心數(shù)是有限的,比如8核CPU,同時只能運行8個線程。開了200個線程,CPU就得在這200個線程之間頻繁切換,每次切換都需要保存當前線程的狀態(tài),加載下一個線程的狀態(tài),這就叫上下文切換。大量的上下文切換消耗了大量的CPU資源,真正處理任務的時間反而少了。就好比你雇了200個工人,但只有8臺機器,工人要不斷地搶機器,搶來搶去,真正干活的時間沒多少。

2. 怎么確定合適的線程數(shù)?

咱可以用一個公式:線程數(shù) = CPU核心數(shù) * (1 + 平均等待時間 / 平均工作時間)。比如一個任務,平均工作時間是1ms,平均等待IO的時間是9ms,那么線程數(shù) = 8 * (1 + 9/1) = 80。這樣可以讓CPU在等待IO的時候,去處理其他線程的任務,提高利用率。

后來我在那個任務處理系統(tǒng)里,把線程數(shù)從200降到80,結果任務處理速度提升了2倍,CPU利用率也降到了合理范圍,系統(tǒng)終于不卡了。

四、偽共享:緩存行對齊沒考慮,性能偷偷溜走

這是個比較隱蔽的坑,我也是在做性能分析的時候,通過工具才發(fā)現(xiàn)的。有兩個共享變量,本來以為沒啥關系,結果它們被放到同一個緩存行里,導致頻繁的緩存失效,性能下降。

1. 啥是偽共享?

CPU緩存是以緩存行為單位的,通常是64字節(jié)。如果兩個不同的變量被放到同一個緩存行里,當一個線程修改了其中一個變量,另一個變量所在的緩存行也會失效,導致另一個線程不得不從主內存重新讀取數(shù)據。比如有兩個long類型的變量,每個8字節(jié),放在一起就是16字節(jié),一個緩存行可以放8個這樣的變量。如果兩個線程分別修改這兩個變量,就會導致緩存行頻繁失效。

2. 怎么解決?

咱可以在變量之間填充一些無用的變量,讓它們不在同一個緩存行里。比如Java里可以用@Contended注解,不過需要在JVM啟動時加上-XX:EnableContended參數(shù)?;蛘咦约菏謩犹畛?,比如定義一個類,里面有幾個long類型的變量,把需要避免偽共享的變量隔開。

我在一個計數(shù)器類里,有兩個計數(shù)器變量count1和count2,被多個線程分別修改,結果發(fā)現(xiàn)它們在同一個緩存行里。后來在中間填充了7個long類型的變量,讓每個變量單獨占一個緩存行,性能提升了30%。

五、volatile用錯地方:以為能保證原子性,結果出大問題

知道volatile能保證可見性,就以為它能保證原子性,在一個自增操作里用了volatile變量,結果并發(fā)情況下,數(shù)值還是不對。

1. volatile的特性

volatile只能保證可見性,不能保證原子性。比如i++這個操作,實際上分為讀取、加1、寫入三個步驟,這三個步驟不是原子的,在多線程情況下,可能會出現(xiàn)丟失更新的情況。

2. 正確使用場景

volatile適合用在狀態(tài)標志位,比如一個線程等待另一個線程完成某個操作,用volatile變量來通知。如果需要原子性操作,還是得用synchronized或者AtomicInteger等原子類。

我之前在一個狀態(tài)機里,用volatile變量來控制狀態(tài)轉換,結果在并發(fā)情況下,狀態(tài)轉換出現(xiàn)了混亂。后來換成用synchronized來保護狀態(tài)轉換的代碼塊,問題就解決了。

六、線程安全的單例模式:雙重檢查鎖定,鎖的對象錯了

寫單例模式的時候,想著用雙重檢查鎖定來提高效率,結果鎖的對象用了類的實例,而不是類的Class對象,導致出現(xiàn)多個實例的情況。

1. 正確的雙重檢查鎖定

正確的做法是鎖的對象是類的Class對象,而且實例變量要用volatile修飾,防止指令重排。比如:

public class Singleton {
    privatevolatilestatic Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. 為啥鎖對象不能是實例

如果鎖的對象是實例,在實例還沒創(chuàng)建的時候,多個線程同時進入,都去創(chuàng)建實例,就會出現(xiàn)多個實例的情況。而用Class對象,不管實例有沒有創(chuàng)建,鎖都是唯一的。

我之前就是鎖的對象錯了,導致系統(tǒng)里出現(xiàn)了多個單例實例,引發(fā)了一系列奇怪的問題,排查了好久才發(fā)現(xiàn)是這個問題。

七、總結:多線程優(yōu)化的正確姿勢

說了這么多坑,咱來總結一下多線程優(yōu)化的正確姿勢:

  1. 線程池參數(shù)別拍腦袋,根據任務類型和系統(tǒng)資源仔細計算,用有界隊列和合適的拒絕策略。
  2. 鎖的粒度要小,類型要選對,能不用鎖就不用鎖,能用原子類就用原子類。
  3. 線程數(shù)不是越多越好,考慮上下文切換的開銷,用公式計算合適的線程數(shù)。
  4. 注意偽共享問題,尤其是在高并發(fā)場景下,用 @Contended 或者手動填充來避免。
  5. volatile 和 synchronized、原子類的適用場景要分清,別搞錯了。
  6. 寫線程安全的代碼時,細節(jié)很重要,比如單例模式的雙重檢查鎖定,鎖的對象和 volatile 修飾符都不能少。

多線程優(yōu)化就像走鋼絲,看著簡單,其實處處都是陷阱。咱得把基礎打扎實,多在實踐中總結經驗,遇到問題別慌,用調試工具和性能分析工具慢慢排查。希望大家看完這篇文章,能避開這些坑,在多線程優(yōu)化的路上少走彎路,寫出高效、穩(wěn)定的代碼。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2021-10-15 06:49:37

MySQL

2024-09-29 09:27:10

2024-09-27 09:31:25

2021-09-25 13:05:10

MYSQL開發(fā)數(shù)據庫

2025-05-27 08:45:00

2025-05-23 08:00:00

VLAN虛擬局域網網絡

2025-04-03 12:30:00

C 語言隱式類型轉換代碼

2019-10-30 14:44:41

Prometheus開源監(jiān)控系統(tǒng)

2024-10-08 08:14:08

用戶生命周期分析服務

2015-03-24 16:29:55

默認線程池java

2022-04-26 21:49:55

Spring事務數(shù)據庫

2024-04-01 08:05:27

Go開發(fā)Java

2024-01-22 09:16:47

多線程性能優(yōu)化

2017-07-17 15:46:20

Oracle并行機制

2023-12-14 17:34:22

Kubernetes集群K8s

2019-09-25 15:30:15

2024-06-26 10:37:05

2025-06-03 06:30:05

2025-02-06 07:45:44

2018-01-10 13:40:03

數(shù)據庫MySQL表設計
點贊
收藏

51CTO技術棧公眾號