Java 中的多線程你只要看這一篇就夠了


如果對什么是線程聂喇、什么是進程仍存有疑惑蔚携,請先Google之希太,因為這兩個概念不在本文的范圍之內(nèi)。

用多線程只有一個目的誊辉,那就是更好的利用cpu的資源亡脑,因為所有的多線程代碼都可以用單線程來實現(xiàn)堕澄。說這個話其實只有一半對,因為反應(yīng)“多角色”的程序代碼霉咨,最起碼每個角色要給他一個線程吧蛙紫,否則連實際場景都無法模擬途戒,當然也沒法說能用單線程來實現(xiàn):比如最常見的“生產(chǎn)者,消費者模型”唁毒。

我先介紹下自己的群:Java大神交流群 561614305 備注好信息 阿里Java高級大牛直播講解知識點星爪,分享知識,多年工作經(jīng)驗的梳理和總結(jié)移必,帶著大家全面毡鉴、科學地建立自己的技術(shù)體系和技術(shù)認知秒赤!

很多人都對其中的一些概念不夠明確憎瘸,如同步、并發(fā)等等幌甘,讓我們先建立一個數(shù)據(jù)字典,以免產(chǎn)生誤會酥诽。

多線程:指的是這個程序(一個進程)運行時產(chǎn)生了不止一個線程

并行與并發(fā):

并行:多個cpu實例或者多臺機器同時執(zhí)行一段處理邏輯皱埠,是真正的同時。

并發(fā):通過cpu調(diào)度算法边器,讓用戶看上去同時執(zhí)行,實際上從cpu操作層面不是真正的同時恒界。并發(fā)往往在場景中有公用的資源,那么針對這個公用的資源往往產(chǎn)生瓶頸十酣,我們會用TPS或者QPS來反應(yīng)這個系統(tǒng)的處理能力际长。

并發(fā)與并行

線程安全:經(jīng)常用來描繪一段代碼。指在并發(fā)的情況之下洋幻,該代碼經(jīng)過多線程使用翅娶,線程的調(diào)度順序不影響任何結(jié)果文留。這個時候使用多線程竭沫,我們只需要關(guān)注系統(tǒng)的內(nèi)存,cpu是不是夠用即可森书。反過來,線程不安全就意味著線程的調(diào)度順序會影響最終結(jié)果凛膏,如不加事務(wù)的轉(zhuǎn)賬代碼:

void transferMoney(Userfrom,Userto, float amount){

同步:Java中的同步指的是通過人為的控制和調(diào)度,保證共享資源的多線程訪問成為線程安全台谍,來保證結(jié)果的準確吁断。如上面的代碼簡單加入@synchronized關(guān)鍵字。在保證結(jié)果準確的同時仔役,提高性能,才是優(yōu)秀的程序炎咖。線程安全的優(yōu)先級高于性能。

好了乘盼,讓我們開始吧俄烁。我準備分成幾部分來總結(jié)涉及到多線程的內(nèi)容:

扎好馬步:線程的狀態(tài)

內(nèi)功心法:每個對象都有的方法(機制)

太祖長拳:基本線程類

九陰真經(jīng):高級多線程控制類

扎好馬步:線程的狀態(tài)

先來兩張圖:

線程狀態(tài)

線程狀態(tài)轉(zhuǎn)換

各種狀態(tài)一目了然,值得一提的是"blocked"這個狀態(tài):

線程在Running的過程中可能會遇到阻塞(Blocked)情況

調(diào)用join()和sleep()方法粹胯,sleep()時間結(jié)束或被打斷辰企,join()中斷,IO完成都會回到Runnable狀態(tài),等待JVM的調(diào)度牢贸。

調(diào)用wait(),使該線程處于等待池(wait blocked pool),直到notify()/notifyAll()臭增,線程被喚醒被放到鎖定池(lock blocked pool )竹习,釋放同步鎖使線程回到可運行狀態(tài)(Runnable)

對Running狀態(tài)的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(tài)(Runnable)。

此外整陌,在runnable狀態(tài)的線程是處于被調(diào)度的線程瞎领,此時的調(diào)度順序是不一定的随夸。Thread類中的yield方法可以讓一個running狀態(tài)的線程轉(zhuǎn)入runnable。

內(nèi)功心法:每個對象都有的方法(機制)

synchronized, wait, notify 是任何對象都具有的同步工具逃魄。讓我們先來了解他們

monitor

他們是應(yīng)用于同步問題的人工線程調(diào)度工具伍俘。講其本質(zhì),首先就要明確monitor的概念癌瘾,Java中的每個對象都有一個監(jiān)視器饵溅,來監(jiān)測并發(fā)代碼的重入。在非多線程編碼時該監(jiān)視器不發(fā)揮作用蜕企,反之如果在synchronized 范圍內(nèi),監(jiān)視器發(fā)揮作用幸乒。

wait/notify必須存在于synchronized塊中唇牧。并且,這三個關(guān)鍵字針對的是同一個監(jiān)視器(某對象的監(jiān)視器)丐重。這意味著wait之后,其他線程可以進入同步塊執(zhí)行臀蛛。

當某代碼并不持有監(jiān)視器的使用權(quán)時(如圖中5的狀態(tài)崖蜜,即脫離同步塊)去wait或notify掺栅,會拋出java.lang.IllegalMonitorStateException纳猪。也包括在synchronized塊中去調(diào)用另一個對象的wait/notify,因為不同對象的監(jiān)視器不同沙绝,同樣會拋出此異常。

再講用法:

synchronized單獨使用:

代碼塊:如下闪檬,在多線程環(huán)境下,synchronized塊中的方法獲取了lock實例的monitor虚循,如果實例相同样傍,那么只有一個線程能執(zhí)行該塊內(nèi)容

publicclassThread1implementsRunnable{

直接用于方法: 相當于上面代碼中用lock來鎖定的效果,實際獲取的是Thread1類的monitor衫哥。更進一步,如果修飾的是static方法膛锭,則鎖定該類所有實例蚊荣。

publicclassThread1implementsRunnable{publicsynchronizedvoidrun(){

synchronized, wait, notify結(jié)合:典型場景生產(chǎn)者消費者問題

/**

volatile

多線程的內(nèi)存模型:main memory(主存)、working memory(線程棧)互例,在處理數(shù)據(jù)時,線程會把值從主存load到本地棧俊马,完成操作后再save回去(volatile關(guān)鍵詞的作用:每次針對該變量的操作都激發(fā)一次load and save)肩杈。

volatile

針對多線程使用的變量如果不是volatile或者final修飾的,很有可能產(chǎn)生不可預(yù)知的結(jié)果(另一個線程修改了這個值扩然,但是之后在某線程看到的是修改之前的值)。其實道理上講同一實例的同一屬性本身只有一個副本界睁。但是多線程是會緩存值的兵拢,本質(zhì)上,volatile就是不去緩存说铃,直接取值嘹履。在線程安全的情況下加volatile會犧牲性能债热。

太祖長拳:基本線程類

基本線程類指的是Thread類,Runnable接口焕刮,Callable接口

Thread 類實現(xiàn)了Runnable接口墙杯,啟動一個線程的方法:

MyThread my= new MyThread();

Thread類相關(guān)方法:

//當前線程可轉(zhuǎn)讓cpu控制權(quán),讓別的就緒狀態(tài)線程運行(切換)publicstaticThread.yield()

關(guān)于中斷:它并不像stop方法那樣會中斷一個正在運行的線程霍转。線程會不時地檢測中斷標識位一汽,以判斷線程是否應(yīng)該被中斷(中斷標識值是否為true)召夹。終端只會影響到wait狀態(tài)、sleep狀態(tài)和join狀態(tài)监憎。被打斷的線程會拋出InterruptedException。

Thread.interrupted()檢查當前線程是否發(fā)生中斷偷霉,返回boolean

synchronized在獲鎖的過程中是不能被中斷的褐筛。

中斷是一個狀態(tài)!interrupt()方法只是將這個狀態(tài)置為true而已渔扎。所以說正常運行的程序不去檢測狀態(tài),就不會終止残吩,而wait等阻塞方法會去檢查并拋出異常倘核。如果在正常運行的程序中添加while(!Thread.interrupted()) ,則同樣可以在中斷后離開代碼體

Thread類最佳實踐:

寫的時候最好要設(shè)置線程名稱 Thread.name活尊,并設(shè)置線程組 ThreadGroup,目的是方便管理酬凳。在出現(xiàn)問題的時候,打印線程棧 (jstack -pid) 一眼就可以看出是哪個線程出的問題稠屠,這個線程是干什么的翎苫。

如何獲取線程中的異常

不能用try,catch來獲取線程中的異常

Runnable

與Thread類似

Callable

future模式:并發(fā)模式的一種,可以有兩種形式攘蔽,即無阻塞和阻塞呐粘,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態(tài)

ExecutorService e = Executors.newFixedThreadPool(3);//submit方法有多重參數(shù)版本作岖,及支持callable也能夠支持runnable接口類型.

九陰真經(jīng):高級多線程控制類

以上都屬于內(nèi)功心法,接下來是實際項目中常用到的工具了辕万,Java1.5提供了一個非常高效實用的多線程包:java.util.concurrent, 提供了大量高級工具,可以幫助開發(fā)者編寫高效沉删、易維護、結(jié)構(gòu)清晰的Java多線程程序砖茸。

1.ThreadLocal類

用處:保存線程的獨立變量脯倚。對一個線程類(繼承自Thread)

當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本推正,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本再沧。常用于用戶登錄控制尊残,如記錄session信息淤堵。

實現(xiàn):每個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map顷扩,功能與map一樣,區(qū)別是桶里放的是entry而不是entry的鏈表扎阶。功能還是一個map婶芭。)以本身為key,以目標為value犀农。

主要方法是get()和set(T a),set之后在map里維護一個threadLocal -> a赁濒,get時將a返回仇穗。ThreadLocal是一個特殊的容器戚绕。

2.原子類(AtomicInteger、AtomicBoolean……)

如果使用atomic wrapper class如atomicInteger耘子,或者使用自己保證原子的操作球切,則等同于synchronized

//返回值為booleanAtomicInteger.compareAndSet(intexpect,intupdate)

該方法可用于實現(xiàn)樂觀鎖,考慮文中最初提到的如下場景:a給b付款10元吨凑,a扣了10元,b要加10元糙臼。此時c給b2元恩商,但是b的加十元代碼約為:

if(b.value.compareAndSet(old,value)){return;

AtomicReference

對于AtomicReference 來講,也許對象會出現(xiàn)揽乱,屬性丟失的情況,即oldObject == current凰棉,但是oldObject.getPropertyA != current.getPropertyA。

這時候探橱,AtomicStampedReference就派上用場了绘证。這也是一個很常用的思路,即加上版本號

3.Lock類

lock: 在java.util.concurrent包內(nèi)胞枕。共有三個實現(xiàn):

ReentrantLock

ReentrantReadWriteLock.ReadLock

ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一樣魏宽, 兩者都是為了解決同步問題,處理資源爭端而產(chǎn)生的技術(shù)队询。功能類似但有一些區(qū)別。

區(qū)別如下:

lock更靈活铆惑,可以自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的后解順序)

提供多種加鎖方案送膳,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有trylock的帶超時時間版本撕阎。

本質(zhì)上和監(jiān)視器鎖(即synchronized是一樣的)

能力越大碌补,責任越大,必須控制好加鎖和解鎖厦章,否則會導致災(zāi)難。

和Condition類的結(jié)合坑律。

性能更高,對比如下圖:

synchronized和Lock性能對比

ReentrantLock

可重入的意義在于持有鎖的線程可以繼續(xù)持有冀值,并且要釋放對等的次數(shù)后才真正釋放該鎖宫屠。

使用方法是:

1.先new一個實例

staticReentrantLock r=newReentrantLock();

2.加鎖

r.lock()或r.lockInterruptibly();

此處也是個不同,后者可被打斷抵栈。當a線程lock后坤次,b線程阻塞,此時如果是lockInterruptibly缰猴,那么在調(diào)用b.interrupt()之后,b線程退出阻塞闷堡,并放棄對資源的爭搶疑故,進入catch塊。(如果使用后者纵势,必須throw interruptable exception 或catch)

3.釋放鎖

r.unlock()

必須做吨悍!何為必須做呢,要放在finally里面育瓜。以防止異常跳出了正常流程栽烂,導致災(zāi)難。這里補充一個小知識點焰手,finally是可以信任的:經(jīng)過測試怀喉,哪怕是發(fā)生了OutofMemoryError,finally塊中的語句執(zhí)行也能夠得到保證躲履。

ReentrantReadWriteLock

可重入讀寫鎖(讀寫鎖的一個實現(xiàn))

ReentrantReadWriteLocklock=newReentrantReadWriteLock()

兩者都有l(wèi)ock,unlock方法。寫寫工猜,寫讀互斥;讀讀不互斥史侣∥荷恚可以實現(xiàn)并發(fā)讀的高效線程安全代碼

4.容器類

這里就討論比較常用的兩個:

BlockingQueue

ConcurrentHashMap

BlockingQueue

阻塞隊列。該類是java.util.concurrent包下的重要類李皇,通過對Queue的學習可以得知宙枷,這個queue是單向隊列,可以在隊列頭添加元素和在隊尾刪除或取出元素慰丛。類似于一個管  道,特別適用于先進先出策略的一些應(yīng)用場景哪亿。普通的queue接口主要實現(xiàn)有PriorityQueue(優(yōu)先隊列)贤笆,有興趣可以研究

BlockingQueue在隊列的基礎(chǔ)上添加了多線程協(xié)作的功能:

BlockingQueue

除了傳統(tǒng)的queue功能(表格左邊的兩列)之外芥永,還提供了阻塞接口put和take,帶超時功能的阻塞接口offer和poll埋涧。put會在隊列滿的時候阻塞,直到有空間時被喚醒劲弦;take在隊 列空的時候阻塞醇坝,直到有東西拿的時候才被喚醒。用于生產(chǎn)者-消費者模型尤其好用,堪稱神器砸琅。

常見的阻塞隊列有:

ArrayListBlockingQueue

LinkedListBlockingQueue

DelayQueue

SynchronousQueue

ConcurrentHashMap

高效的線程安全哈希map夜赵。請對比hashTable , concurrentHashMap, HashMap

5.管理類

管理類的概念比較泛,用于管理線程摊腋,本身不是多線程的嘁傀,但提供了一些機制來利用上述的工具做一些封裝。

了解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統(tǒng)級管理類 ThreadMXBean

ThreadPoolExecutor

如果不了解這個類橙凳,應(yīng)該了解前面提到的ExecutorService笑撞,開一個自己的線程池非常方便:

ExecutorService e= Executors.newCachedThreadPool();

該類內(nèi)部是通過ThreadPoolExecutor實現(xiàn)的,掌握該類有助于理解線程池的管理茴肥,本質(zhì)上,他們都是ThreadPoolExecutor類的各種實現(xiàn)版本瞬铸。請參見javadoc:

ThreadPoolExecutor參數(shù)解釋

翻譯一下:

corePoolSize:池內(nèi)線程初始值與最小值础锐,就算是空閑狀態(tài)皆警,也會保持該數(shù)量線程。

maximumPoolSize:線程最大值耀怜,線程的增長始終不會超過該值。

keepAliveTime:當池內(nèi)線程數(shù)高于corePoolSize時,經(jīng)過多少時間多余的空閑線程才會被回收从诲。回收前處于wait狀態(tài)

unit:

時間單位俊性,可以使用TimeUnit的實例,如TimeUnit.MILLISECONDS

workQueue:待入任務(wù)(Runnable)的等待場所趟薄,該參數(shù)主要影響調(diào)度策略典徊,如公平與否脓规,是否產(chǎn)生餓死(starving)

threadFactory:線程工廠類,有默認實現(xiàn)寝姿,如果有自定義的需要則需要自己實現(xiàn)ThreadFactory接口并作為參數(shù)傳入。

請注意:該類十分常用腰湾,作者80%的多線程問題靠他。

Java大神交流群 561614305 備注好信息 阿里Java高級大牛直播講解知識點倒槐,分享知識葵萎,多年工作經(jīng)驗的梳理和總結(jié),帶著大家全面谎痢、科學地建立自己的技術(shù)體系和技術(shù)認知卷雕!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滨嘱,隨后出現(xiàn)的幾起案子浸间,更是在濱河造成了極大的恐慌,老刑警劉巖魁蒜,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锥咸,居然都是意外死亡搏予,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門碗殷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來校镐,“玉大人,你說我怎么就攤上這事鸟廓。” “怎么了牍陌?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵员咽,是天一觀的道長。 經(jīng)常有香客問我契讲,道長滑频,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任银伟,我火速辦了婚禮绘搞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘琉预。我一直安慰自己蒿褂,他們只是感情好尖阔,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布榨咐。 她就那樣靜靜地躺著谴供,像睡著了一般。 火紅的嫁衣襯著肌膚如雪数焊。 梳的紋絲不亂的頭發(fā)上崎场,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音干厚,去河邊找鬼螃宙。 笑死,一個胖子當著我的面吹牛挂捅,可吹牛的內(nèi)容都是我干的堂湖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼伺糠,長吁一口氣:“原來是場噩夢啊……” “哼酱讶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泻肯,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤灶挟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后稚铣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墅垮,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡算色,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年螟够,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片若河。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡萧福,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲫忍,到底是詐尸還是另有隱情炭庙,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布逾雄,位于F島的核電站腻脏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏做鹰。R本人自食惡果不足惜鼎姐,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饭尝。 院中可真熱鬧献宫,春花似錦钥平、人聲如沸涉瘾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囚巴。三九已至,卻和暖如春彤叉,著一層夾襖步出監(jiān)牢的瞬間村怪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工柬焕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梭域,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓富玷,卻偏偏與公主長得像既穆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子幻工,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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