1. 并發(fā)和并行
- 并發(fā)是指同一個(gè)時(shí)間段內(nèi)多個(gè)任務(wù)同時(shí)都在執(zhí)行报嵌,而單位時(shí)間內(nèi)只有一個(gè)任務(wù)在執(zhí)行阀趴,單個(gè)cpu通過切換線程上下文實(shí)現(xiàn)。
- 并行是說在單位時(shí)間內(nèi)多個(gè)任務(wù)同時(shí)在不同cpu上執(zhí)行。
2. 并發(fā)的多面性
線程安全性:
- 共享資源可以被多個(gè)線程所持有或者說多個(gè)線程都可以去訪問該資源。
- 線程安全性是指當(dāng)多個(gè)線程同時(shí)讀寫一個(gè)共享資源并且沒有任何同步措施時(shí)赌髓,導(dǎo)致出現(xiàn)臟數(shù)據(jù)或者其他不可預(yù)見的結(jié)果的問題。
- 如果多個(gè)線程都只是讀取共享資源催跪,而不去修改锁蠕,那么就不會(huì)存在線程安全問題。
內(nèi)存可見性問題:
- 線程讀寫變量時(shí)操作的是自己工作內(nèi)存(如Ll 或者L2 緩存或者CPU 的寄存器)中的變量懊蒸。
- 當(dāng)一個(gè)線程操作共享變量時(shí)荣倾, 它首先從主內(nèi)存復(fù)制共享變量到自己的工作內(nèi)存, 然后對工作內(nèi)存里的變量進(jìn)行處理骑丸, 處理完后將變量值更新到主內(nèi)存舌仍。
- 由于線程可能通過緩存命中讀取到需要的值,我們無法確保執(zhí)行讀操作的線程能適時(shí)地看到其他線程寫入主內(nèi)存的值通危。
指令重排序:
- JAVA允許編譯器和處理器對指令重排序以提高運(yùn)行性能铸豁, 并且只會(huì)對不存在數(shù)據(jù)依賴性的指令重排序。在單線程下重排序可以保證最終執(zhí)行的結(jié)果與程序順序執(zhí)行的結(jié)果一致菊碟。
- 重排序在多線程下會(huì)導(dǎo)致非預(yù)期的程序執(zhí)行結(jié)果
- 使用volatile修飾變量避免重排序和內(nèi)存可見性問題节芥。寫volatile 變量時(shí),可以確保volatile 寫之前的操作不會(huì)被編譯器重排序到volatile 寫之后框沟。讀volatile 變量時(shí),可以確保volatile 讀之后的操作不會(huì)被編譯器重排序到volatile讀之前增炭。
3. synchronized
- synchronized 塊是Java 提供的一種原子性內(nèi)置鎖忍燥,是排他鎖,經(jīng)常被用來實(shí)現(xiàn)原子性操作
- 線程的執(zhí)行代碼在進(jìn)入synchronized 代碼塊前會(huì)自動(dòng)獲取內(nèi)部鎖隙姿,這時(shí)候其他線程訪問該同步代碼塊時(shí)會(huì)被阻塞掛起梅垄。
- 拿到內(nèi)部鎖的線程會(huì)在正常退出同步代碼塊或者拋出異常后或者在同步塊內(nèi)調(diào)用了該內(nèi)置鎖資源的wait 系列方法時(shí)釋放該內(nèi)置鎖。
- synchronized 的使用就會(huì)導(dǎo)致上下文切換输玷。當(dāng)阻塞一個(gè)線程時(shí)队丝,需要從用戶態(tài)切換到內(nèi)核態(tài)執(zhí)行阻塞操作。
- 保證內(nèi)存可見性:
進(jìn)入synchronized塊直接從主內(nèi)存中獲取共享變量欲鹏。
退出synchronized 塊把在synchronized 塊內(nèi)對共享變量的修改刷新到主內(nèi)存机久。
4. volatile
- 對于解決內(nèi)存可見性問題, Java 提供了一種弱形式的同步volatile赔嚎,該關(guān)鍵字可以確保對一個(gè)變量的更新對其他線程馬上可見膘盖。
- 當(dāng)一個(gè)變量被聲明為volatile 時(shí)胧弛,線程在寫入變量時(shí)不會(huì)把值緩存在寄存器或者其他地方,而是會(huì)把值刷新回主內(nèi)存侠畔。
- 當(dāng)其他線程讀取該共享變量時(shí)结缚,會(huì)從主內(nèi)存重新獲取最新值,而不是使用當(dāng)前線程的工作內(nèi)存中的值软棺。
- synchronized同樣可以解決內(nèi)存可見性問題红竭,但它是獨(dú)占鎖,會(huì)存在線程上下文切換和線程重新調(diào)度的開銷喘落。
- 使用情況:
【1】寫入變量值不依賴變量的當(dāng)前值時(shí)(這屬于讀取-計(jì)算-寫入茵宪,volatile 不保證原子性)
【2】讀寫變量值時(shí)沒有加鎖,加鎖就不需要volatile了揖盘。
5. 鎖
樂觀鎖和悲觀鎖:
- 悲觀鎖指對數(shù)據(jù)被外界修改持保守態(tài)度眉厨,認(rèn)為數(shù)據(jù)很容易就會(huì)被其他線程修改,所以在數(shù)據(jù)被處理前先對數(shù)據(jù)進(jìn)行加鎖兽狭,并在整個(gè)數(shù)據(jù)處理過程中憾股,使數(shù)據(jù)處于鎖定狀態(tài)。在數(shù)據(jù)庫中箕慧,在對數(shù)據(jù)記錄操作前給記錄加排它鎖服球。如果獲取鎖失敗, 則說明數(shù)據(jù)正在被其他線程修改颠焦, 當(dāng)前線程則等待或者拋出異常斩熊。如果獲取鎖成功,則對記錄進(jìn)行操作伐庭,然后提交事務(wù)后釋放排它鎖粉渠。
- 樂觀鎖是相對悲觀鎖來說的,它認(rèn)為數(shù)據(jù)在一般情況下不會(huì)造成沖突圾另,所以在訪問記錄前不會(huì)加排它鎖霸株,而是在進(jìn)行數(shù)據(jù)提交更新時(shí),才會(huì)正式對數(shù)據(jù)沖突與否進(jìn)行檢測集乔。
公平鎖與非公平鎖(根據(jù)線程獲取鎖的搶占機(jī)制):
-
公平鎖表示線程獲取鎖的順序是按照線程請求鎖的時(shí)間早晚來決定的
ReentrantLock pairLock =new ReentrantLock(true) - 非公平鎖則是先來不一定先得去件。ReentrantLock默認(rèn)構(gòu)造函數(shù)是非公平鎖。
- 在沒有公平性需求的前提下盡量使用非公平鎖扰路,因?yàn)楣芥i會(huì)帶來性能開銷尤溜。
獨(dú)占鎖與共享鎖(根據(jù)鎖只能被單個(gè)線程持有還是能被多個(gè)線程共同持有):
-
獨(dú)占鎖保證任何時(shí)候都只有一個(gè)線程能得到鎖,如ReentrantLock汗唱。
獨(dú)占鎖是一種悲觀鎖宫莱,這限制了并發(fā)性,因?yàn)樽x操作并不會(huì)影響數(shù)據(jù)的一致性 -
共享鎖則可以同時(shí)由多個(gè)線程持有哩罪,例如ReadWriteLock 讀寫鎖梢睛,它允許一個(gè)資源可以被多線程同時(shí)進(jìn)行讀操作肥印。
共享鎖是一種樂觀鎖
可重入鎖:
- 當(dāng)一個(gè)線程再次獲取它自己已經(jīng)獲取的鎖時(shí)如果不被阻塞,那么我們說該鎖是可重入的绝葡。
- synchronized 內(nèi)部鎖是可重入鎖
- 原理是在鎖內(nèi)部維護(hù)一個(gè)線程標(biāo)示深碱,用來標(biāo)示該鎖目前被哪個(gè)線程占用,然后關(guān)聯(lián)一個(gè)計(jì)數(shù)器藏畅。一開始計(jì)數(shù)器值為0,說明該鎖沒有被任何線程占用敷硅。當(dāng)一個(gè)錢程獲取了該鎖時(shí),計(jì)數(shù)器的值會(huì)變成1愉阎,當(dāng)獲取了該鎖的線程再次獲取鎖時(shí)發(fā)現(xiàn)鎖擁有者是自己绞蹦,就會(huì)把計(jì)數(shù)器值加+1,當(dāng)釋放鎖后計(jì)數(shù)器值-1 。當(dāng)計(jì)數(shù)器值為0 時(shí)鎖里面的線程標(biāo)示被重置為null榜旦。此時(shí)喚醒其他被阻塞的線程來競爭鎖幽七。
自旋鎖:
- 當(dāng)一個(gè)線程在獲取鎖(比如獨(dú)占鎖)失敗后,會(huì)被切換到內(nèi)核態(tài)而被掛起溅呢。當(dāng)該線程獲取到鎖時(shí)又需要將其切換到內(nèi)核狀態(tài)而喚醒該線程澡屡。這帶來了開銷。
- 自旋鎖使用CPU 時(shí)間換取線程阻塞與調(diào)度的開銷咐旧,當(dāng)前線程在獲取鎖時(shí)驶鹉,如果發(fā)現(xiàn)鎖已經(jīng)被其他線程占有,它不馬上阻塞自己铣墨,在不放棄CPU 使用權(quán)的情況下室埋,多次嘗試獲取(默認(rèn)次數(shù)是10 伊约,可以使用-XX:PreBlockSpinsh 參數(shù)設(shè)置)姚淆,直到達(dá)到最大次數(shù)才掛起或獲取到鎖。