多線程與高并發(fā)知識點簡述

CAS和Atomic包

CAS操作流程:

傳入初始值和待修改值 -> 讀取當前值 -> 比較當前值和傳入的初始值 -> 
如果相等理张,將值改成待修改值先巴;如果不等則退出

        int current = get();  //第一次讀取颊亮,作為初始值
        int next = current + 1; //待修改值
        if (compareAndSet(current, next)) //第二次讀取钧栖,比較初始值和當前值

JDK5之后發(fā)布了基于樂觀鎖思想的自旋鎖(無鎖)java.util.concurrent.atomic包邻吞。包里的很多操作壶谒,最后都調(diào)用到了compareAndSwapXXX方法:

//設值
AtomicXXX.compareAndSet ->
sun.misc.Unsafe.compareAndSwapXXX ->
通過JNI調(diào)用Native實現(xiàn) ->
匯編指令(asm)直接支持lock cmpxchg ->  //多核處理器會加上lock保證CAS操作的原子性
如果CAS失敗仆救,自旋

cmpxchg本身并不是原子操作抒和,但通過lock,實現(xiàn)了原子操作派桩。
自旋鎖就是在CAS失敗的情況下重復進行CAS直到CAS成功构诚,從而在沒有鎖的狀態(tài)下保證對一個值的正確更新。

如果兩次判斷雖然值相等铆惑,但其實是經(jīng)過其他線程的修改范嘱,只是恰好改回到了之前取數(shù)據(jù)時的值,這就叫ABA問題员魏。解決問題的方法就是增加一個版本號丑蛤,不僅僅通過檢查值得變化來確定是否更新。

Java對象布局

通過JOL查看沒上鎖的Object對象布局(64位系統(tǒng)):

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

對象在內(nèi)存中的布局

markword:記錄hashcode撕阎、GC的分代年齡信息(4bit受裹,記錄沒能被GC的次數(shù)。幾次被強制GC可配置)和鎖信息。Object中占8byte
class printer:類型指針信息棉饶。指針大小也和系統(tǒng)總線長度有關(guān)厦章,如果是64位系統(tǒng),指針大小應該是8byte照藻。但默認JVM開啟了CompressedClassPointers袜啃,class printer中的指針大小會被壓縮成4byte。Object中占4byte
Instance data:對象中的成員數(shù)據(jù)幸缕。默認開啟CompressedOops后群发,普通成員變量指針也會被壓縮成4byte。Object中占0byte
padding:對齊位发乔。對象大小需要能被總整byte數(shù)整除熟妓。比如64位系統(tǒng),總線是64/8=8byte栏尚,所以對象大小需要能被8整除起愈。Object中占4byte

synchronized

查看匯編指令,synchronized關(guān)鍵字在同步代碼塊前后加入了monitorenter和monitorexit這兩個指令译仗。monitorenter指令會獲取鎖對象告材,如果獲取到了鎖對象,就將鎖計數(shù)器加1古劲,未獲取到則會阻塞當前線程。monitorexit指令會釋放鎖對象缰猴,同時將鎖計數(shù)器減1产艾。
執(zhí)行過程:
1,字節(jié)碼:monitorenter moniterexit監(jiān)控鎖
2滑绒,鎖自動升級

鎖升級過程

最早synchronized一上來就是重量級鎖闷堡,后來JDK優(yōu)化了synchronized,有一個鎖升級過程:

//new -> 只有自己用時 -> 有競爭時開搶疑故,嘗試將線程棧中Lock Record指針指向自身的markword -> 
//自旋次數(shù)超過JVM設置的次數(shù)杠览,向內(nèi)核申請重量級鎖(mutex),通過隊列管理請求
1)無鎖態(tài) - 2)偏向鎖 - 3)輕量級鎖(自旋鎖纵势、無鎖) - 4)重量級鎖

具體一個對象目前處于什么鎖狀態(tài)踱阿,可以查看對象markword中的標志位。

鎖消除

StringBuffer是線程安全的钦铁,因為它的關(guān)鍵方法都是被synchronized修飾過的软舌。但如果JVM發(fā)現(xiàn)StringBuffer沒有多線程引用,就會自動消除StringBuffer對象內(nèi)部的鎖牛曹。

volatile

保證線程可見性(線程間數(shù)據(jù)同步)和指令有序性佛点。
保證有序性的意義:一個對象new的過程分為三步,1)分配內(nèi)存、2)初始化超营、3)將對象指向這塊內(nèi)存鸳玩。因為cpu指令重排列的存在,有可能先指向一塊null的內(nèi)存演闭,這時使用可能存在問題不跟。

cache line

緩沖讀數(shù)據(jù),是按塊讀而不是只讀所需數(shù)據(jù)船响。這一塊數(shù)據(jù)叫cache line(緩存行)躬拢,占64byte。cache line中有數(shù)據(jù)被volatile修飾见间,會使其他數(shù)據(jù)也有volatile修飾效果聊闯,可能導致多余的同步操作。注意緩存行對齊可以避免該問題米诉。

cpu的亂序執(zhí)行

cpu并不一定按照匯編代碼順序執(zhí)行(cpu層面菱蔬,只保證單個線程的處理結(jié)果符合預期),如果該特性會對結(jié)果產(chǎn)生不可控的影響史侣。volatile可以避免指令重排序拴泌。
舉例:

class T {
    int m = 8;
}
T t = new T();

匯編指令是 先將一塊內(nèi)存分配給T -> 給m賦值 -> 讓這塊內(nèi)存和t建立連接。但有可能因為指令重排序惊橱,導致建立連接后才賦值蚪腐。

JSR內(nèi)存屏障

根據(jù)Java Specification Requests,JVM發(fā)現(xiàn)有volatile修飾税朴,就會通過以下四種內(nèi)存屏障保證數(shù)據(jù)一致性:

LoadLoad回季、StoreStore、LoadStore正林、StoreLoad

LoadLoad即上一條Load指令執(zhí)行完才能執(zhí)行下一條load指令泡一。具體使用:

StoreStoreBarrier       LoadLoadBarrier       
volatile寫操作           volatile讀操作
StoreLoadBarrier        LoadStoreBarrier   
volatile和synchronized區(qū)別

volatile底層的實現(xiàn),和synchronized一樣觅廓,就是lock鎖總線鼻忠。只不過,volatile鎖的是一條空指令杈绸,synchronized鎖的是CAS操作帖蔓,所以volatile僅僅只保證變量的讀和寫是原子性操作,并不能保證對變量的復合操作(修改)也是原子性的瞳脓。

多線程相關(guān)api

Thread.join

A線程中B線程調(diào)用join讨阻,則A線程阻塞直到Thread執(zhí)行完。期間其他線程的執(zhí)行不受影響篡殷。

Thread.yield()

當前線程讓出CPU時間片钝吮,重新參與競爭。

synchronized + object.wait / object.notify

鎖的等待喚醒。使用前提是必須有鎖奇瘦。

synchronized(o) {
    o.wait();
}

notify通知其他線程棘催;wait 讓持有鎖的當前線程讓出鎖(立馬處于阻塞狀態(tài))。

LockSupport
public static void park(); // 無期限暫停當前線程
public static void unpark(Thread thread); // 恢復當前線程
AQS 非阻塞同步

AbstractQueuedSynchronizer耳标,一個用來構(gòu)建鎖和同步工具的框架醇坝,包括常用的ReentrantLock、CountDownLatch次坡、Semaphore等呼猪。底層都是通過CAS自旋實現(xiàn)的鎖。

Lock lock = new ReentranLock(); //可以設置公平鎖或非公平鎖砸琅,默認非公平鎖
lock.lock();
try{
    //do something
    Condition con = ReentrantLock.newCondition() //獲得的Condition是一個隊列
    //等待Condition的signalAll或signal喚醒
    con.await();
}finally{
    lock.unlock();
}

ReentrantLock的內(nèi)部類Sync繼承了AQS宋距,分為公平鎖FairSync和非公平鎖NonfairSync,默認使用非公平鎖症脂。
公平鎖:線程獲取鎖的順序和調(diào)用lock的順序一樣谚赎,F(xiàn)IFO;
非公平鎖:線程獲取鎖不嚴格按照排隊順序诱篷。
在ReentranLock中壶唤,申請非公平鎖時會先插隊,不行再排隊棕所;申請公平鎖則只能靠排隊闸盔。不管用哪種鎖,如果第一次沒有獲取到鎖琳省,都會被放到一個自旋的隊列中蕾殴,按照順序來不斷嘗試獲取鎖。
ReentranLock支持可重入鎖岛啸,即同一線程可多次獲取鎖而不會引起死鎖。重復獲取鎖的次數(shù)會被記數(shù)茴肥,釋放鎖時減1坚踩。

//初始化計數(shù)器總量為2
CountDownLatch countDownLatch = new CountDownLatch(2);
//計數(shù)器值減一
countDownLatch.countDown();
//阻塞,直到計數(shù)器值為0時解除
countDownLatch.await();
TransferQueue

通過TransferQueue.transfer方法發(fā)送數(shù)據(jù)并阻塞瓤狐,嘗試和另一個TransferQueue.transfer調(diào)用配對瞬铸,配對后可以通過TransferQueue.take()獲取對方的數(shù)據(jù)。

中斷

在java中础锐,從開發(fā)者角度嗓节,將中斷請求發(fā)向一個線程的唯一方式是調(diào)用Thread.interrupt()方法。
非阻塞中的線程, 只是改變了中斷狀態(tài), 即Thread.isInterrupted()將返回true皆警。阻塞中的線程如果收到中斷信號拦宣,會拋出InterruptedException異常將具體響應處理轉(zhuǎn)移給開發(fā)者,同時把中斷狀態(tài)置為true
不可響應中斷線程是指阻塞中的線程或者在運行線程不會對中斷信號作任何響應,如輸入和輸出流類會阻塞等待 I/O 完成鸵隧,但是它們不拋出 InterruptedException绸罗,而且在被中斷的情況下也不會退出阻塞狀態(tài). 然而,對于Socket I/O豆瘫,如果一個線程關(guān)閉套接字珊蟀,則那個套接字上的阻塞 I/O 操作將提前結(jié)束,并拋出一個 SocketException外驱。

存在 InterruptedException拋出的地方糠涛,如果當下不處理,要繼續(xù)向上拋出InterruptedException異常懊烤,以便上層應用能檢測到中斷御吞。 不要丟失 InterruptedException。

LinkedBlockingQueue

兩把線程鎖趟薄,兩個Condition:



線程池

當并發(fā)數(shù)高時建議自定義線程池绽诚,因為Executors返回的線程池對象有以下弊端:
1,F(xiàn)ixedThreadPool和SingleThreadPool允許的請求隊列長度為Integer.MAX_VALUE杭煎,可能堆積大量請求從而導致oom
2恩够,CachedThreadPool允許的創(chuàng)建線程數(shù)量為Integer.MAX_VALUE,可能堆積大量請求從而導致oom
另外羡铲,線程池給的線程默認命名不利于問題調(diào)試蜂桶。

其他參考:
簡單聊聊JDK中的七大阻塞隊列https://www.cnblogs.com/konck/p/9473677.html
有關(guān)線程池的10個問題https://www.cnblogs.com/konck/p/9473681.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市也切,隨后出現(xiàn)的幾起案子扑媚,更是在濱河造成了極大的恐慌,老刑警劉巖雷恃,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆股,死亡現(xiàn)場離奇詭異,居然都是意外死亡倒槐,警方通過查閱死者的電腦和手機旬痹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讨越,“玉大人两残,你說我怎么就攤上這事“芽纾” “怎么了人弓?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長着逐。 經(jīng)常有香客問我崔赌,道長意蛀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任峰鄙,我火速辦了婚禮浸间,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吟榴。我一直安慰自己魁蒜,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布吩翻。 她就那樣靜靜地躺著兜看,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狭瞎。 梳的紋絲不亂的頭發(fā)上细移,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音熊锭,去河邊找鬼弧轧。 笑死,一個胖子當著我的面吹牛碗殷,可吹牛的內(nèi)容都是我干的精绎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锌妻,長吁一口氣:“原來是場噩夢啊……” “哼代乃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仿粹,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤搁吓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吭历,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堕仔,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年晌区,在試婚紗的時候發(fā)現(xiàn)自己被綠了摩骨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡契讲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滑频,到底是詐尸還是另有隱情捡偏,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布峡迷,位于F島的核電站银伟,受9級特大地震影響你虹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彤避,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一傅物、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琉预,春花似錦董饰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娄帖,卻和暖如春也祠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背近速。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工诈嘿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人削葱。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓奖亚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佩耳。 傳聞我的和親對象是個殘疾皇子遂蛀,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353