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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

多线程进阶

發(fā)布時(shí)間:2024/3/13 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多线程进阶 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 進(jìn)程與線程

進(jìn)程:是操作系統(tǒng)中的各個(gè)程序,是資源分配的基本單位,舉例:QQ.exe,分配內(nèi)存、端口號(hào)

線程:是執(zhí)行程序的基本單位,也就是說(shuō)程序中的代碼是由線程執(zhí)行的,一個(gè)進(jìn)程中包含一個(gè)或多個(gè)線程,可以并行執(zhí)行。

進(jìn)程C

進(jìn)程A

進(jìn)程B

線程aO

線程cO

線程aO

線程a

線程bO

線程aO

線程bO

線程bO

線程b

操作系統(tǒng)

CPU1

CPU2

1. 多線程的重要性

多線程是基礎(chǔ),基礎(chǔ)到什么程度?基礎(chǔ)到如果不會(huì)多線程,那么最簡(jiǎn)單的CRUD都寫(xiě)不好。

具個(gè)例子:現(xiàn)在有一個(gè)Person類(lèi),有name屬性,保存時(shí)要求name唯一,

這時(shí)如果兩個(gè)用戶同時(shí)提交且名字一樣,那么后臺(tái)就會(huì)有兩個(gè)線程同時(shí)執(zhí)行 create 方法,最終導(dǎo)致數(shù)據(jù)庫(kù)存入兩條重復(fù)的數(shù)據(jù)。

或許你會(huì)說(shuō)在數(shù)據(jù)庫(kù)表中增加唯一約束,但是如果該表的數(shù)據(jù)只能邏輯刪除,這樣就是有問(wèn)題的。

所以,只有學(xué)好多線程、鎖,這些東西,才能寫(xiě)好代碼。

2. 創(chuàng)建線程的兩種方式

為什么寫(xiě)了4種?

因?yàn)榫W(wǎng)上有種說(shuō)法,把 Callable和線程池 各自列為一種創(chuàng)建線程的方式,

但是我們需要知道其實(shí)它們的在本質(zhì)上都是第二種

3. 線程狀態(tài)

該知識(shí)點(diǎn),了解就行,Thread類(lèi)中有State枚舉

初始

NEW

Thread.sleep(long)

Thread.starto

Object.wait(long)

Object.waito

運(yùn)行

Thread.join(long)

objectjoino

RUNNABLE)

LockSupport.parkNanoso

LockSupport.parko

運(yùn)行中

LockSupport.parkUntilo

RUNNING)

等待

超時(shí)等待

yieldo

WAITING)

系統(tǒng)調(diào)度

(TIMEDWAITING)

系統(tǒng)調(diào)度

object.notifyo

Object.notifyo

就緒(READY

Object.notifyAlo

Object.notifyAio

LockSupport.unpark(Thread)

LockSupport.unpark(Thread)

超進(jìn)時(shí)間到

等待進(jìn)入synchronized方法

等待進(jìn)入synchronized塊

獲取到鎖

執(zhí)行完成

阻寒

終止

BLOCKED)

(TERMINATED)

1. 初始(NEW):新創(chuàng)建了一個(gè)線程對(duì)象,但還沒(méi)有調(diào)用start()方法。

2. 運(yùn)行(RUNNABLE):Java線程中將就緒(ready)和運(yùn)行中(running)兩種狀態(tài)籠統(tǒng)的稱(chēng)為“運(yùn)行”。

線程創(chuàng)建后,且調(diào)用了start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待獲取CPU的使用權(quán),

此時(shí)處于就緒狀態(tài)(ready)。就緒狀態(tài)的線程在獲得CPU時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)。

3. 阻塞(BLOCKED):表示線程等待獲取鎖。

4. 等待(WAITING):進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動(dòng)作(喚醒或打斷)。

5. 超時(shí)等待(TIMED_WAITING):跟WAITING不同,可在指定時(shí)間后自己運(yùn)行,如果等待時(shí)釋放鎖,超時(shí)后阻塞于鎖。

6. 終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢。

狀態(tài)切換時(shí)常用方法

start()

啟動(dòng)一個(gè)線程

run()

線程需要執(zhí)行的代碼,run方法結(jié)束,該線程結(jié)束

sleep(long millis)

線程休眠,但不釋放鎖

join()

等待該線程結(jié)束,可以讓線程順序執(zhí)行

wait()/notify()/notifyAll()

wait()使當(dāng)前線程等待,前提是 必須先獲得鎖,一般配合synchronized 關(guān)鍵字使用

只有當(dāng) notify/notifyAll() 被執(zhí)行時(shí)候,才會(huì)喚醒該線程繼續(xù)執(zhí)行,直到執(zhí)行完synchronized 代碼塊或是再次遇到wait()

notify/notifyAll() 的執(zhí)行只是喚醒等待的線程,而不會(huì)立即釋放鎖,鎖的釋放要看代碼塊的具體執(zhí)行情況。所以盡量在使用了notify/notifyAll() 后立即退出臨界區(qū),以喚醒其他線程讓其獲得鎖

代碼演示

特殊情況下的notify

4. 一些常見(jiàn)問(wèn)題

1. 多個(gè)線程順序執(zhí)行

●使用join方法

●定義一個(gè)共享變量,各個(gè)線程根據(jù)變量執(zhí)行

2. 停止線程

停止線程的最好方式是讓線程正常結(jié)束

●聲明一個(gè)變量,設(shè)置一個(gè)開(kāi)關(guān)

●interrupt方法,interrupt()并不會(huì)終止線程!只是將線程的中斷標(biāo)記設(shè)為true

如果線程在阻塞、睡眠、等待,會(huì)拋出InterruptedException

5. synchronized

1. 字符串加鎖

不建議對(duì)字符串加鎖,字符串比較特殊,一般情況下在內(nèi)存中只有一份兒,兩個(gè)線程分別對(duì)同一個(gè)字符串加鎖,非常容易產(chǎn)生阻塞,甚至是死鎖。而且如果用法不對(duì),加鎖毫無(wú)效果。

2. 鎖升級(jí)過(guò)程

待完善

簡(jiǎn)單過(guò)程

●第一次加鎖,偏向鎖,記錄Thread Id

●另一個(gè)線程來(lái),發(fā)現(xiàn)Thread Id 不同,鎖升級(jí):輕量級(jí)鎖,又稱(chēng) 自旋鎖

●自旋一定次數(shù)后仍然搶不到鎖,升級(jí)為重量級(jí)鎖,線程掛起,等待

JaraSychrontzod原理

Blog.dreamtobe.cn

康星顏鎮(zhèn)

輕量級(jí)鎖

就撐葛阿網(wǎng)步代碼球

目新褲狀變

婚持有省雞鎖購(gòu)?fù)艹藤?gòu)轉(zhuǎn)中分配領(lǐng)記

詩(shī)新電購(gòu)理中牙配鎖記嬰

0108克包

旗新旺甲

oA

星品查的話

持貝對(duì)常失中藥MtWord有桂程區(qū)展中

烤美對(duì)尖中拌心意Word當(dāng)記

cAS鞋作

首致Threas

哈宣時(shí)象尖美NAXRWTOrO中記素的

商貴試

糯象失隆M金KWor中尼泰

羅持有智網(wǎng)休未程慶得控鎖飯鎖

慶瑞

預(yù)肉蘭標(biāo)轉(zhuǎn)程鎖記帖

德南楠祥有皇肉鎖程鎖記的理計(jì)電惠:00

成武萄療有會(huì)雞鎮(zhèn)購(gòu)轉(zhuǎn)摩

特變力童選設(shè)鎖

淘自童皇想統(tǒng)炸升惠板加

原持有售肉鎖轉(zhuǎn)甲

原邦有信向鎖的情程劑達(dá)安會(huì)店

TMONGLLEPOEHLY1西籃肉1志

營(yíng)牛酒蚌有省肉鎖購(gòu)連程

升姨大性蟹德鎖

楓行胃代碼特

怡查湖冷有省向特價(jià)機(jī)

周商當(dāng)新桂程使記最的指什兒特惠食館

未活動(dòng)楓泰/已漂出網(wǎng)步代碼塊

鞋國(guó)被鞋您門(mén)超強(qiáng)鎮(zhèn)司

開(kāi)始斯-輪值茂手

腦欖博有盒方評(píng)的桂程

肉習(xí)磚檔20n爵針

日煲

餐園德鋪

從安全點(diǎn)境傳膚行

開(kāi)始鞋園速餐新樓

1時(shí)牽頭中容MW中2萄量3G指身轉(zhuǎn)程錢(qián)記

oL霞看省育話

腳在海粉鎖記餐摩中W

武婆否省尚肉

婚越鎖

g.dreamtobe.cn

輕量級(jí)和重量級(jí)鎖的使用,輕量級(jí)自旋時(shí)很消耗cpu

如果線程數(shù)少,而且運(yùn)行速度較快,適合輕量級(jí)鎖,反之使用重量級(jí)鎖

2. 線程3個(gè)特性

1. 可見(jiàn)性

在java中,每一個(gè)線程都有一塊工作內(nèi)存,其中存放著主內(nèi)存中的變量值得拷貝,當(dāng)線程執(zhí)行時(shí),它在自己的工作內(nèi)存區(qū)中操作這些變量。

JMM(JAVA內(nèi)存模型)

線程

線程

線程

線程

工作內(nèi)存行1

工作內(nèi)存

工作內(nèi)存

工作內(nèi)存

副本

副本

副本

副本

主內(nèi)存(變量a)

代碼:

volatile 使變量在多個(gè)線程中可見(jiàn),當(dāng)一個(gè)線程修改變量后,強(qiáng)制其他線程到主內(nèi)存中讀取變量值,性能比synchronized強(qiáng),不會(huì)阻塞。但是不具備原子性,不適當(dāng)?shù)氖褂?#xff0c;在CPU層面上極有可能造成計(jì)算速度降低

代碼:

以上代碼運(yùn)行時(shí)間:220毫秒左右,

對(duì)代碼進(jìn)行修改,把變量a加上 volatile,運(yùn)行時(shí)間:3000毫秒左右。

想知道原因,得先弄明白以下幾個(gè)東西:

CPU緩存

工作內(nèi)存 本質(zhì)上就是 CPU緩存 ,是 CPU與內(nèi)存之間的臨時(shí)數(shù)據(jù)區(qū)

CPU

主內(nèi)存

CPU

緩存

為什么需要CPU緩存?

●解決CPU運(yùn)行速度與內(nèi)存讀寫(xiě)速度不匹配的矛盾——緩存的速度比內(nèi)存的速度快多了。

●CPU往往需要重復(fù)處理相同的數(shù)據(jù)、重復(fù)執(zhí)行相同的指令,如果這部分?jǐn)?shù)據(jù)、指令CPU能在CPU緩存中找到,CPU就不需要從內(nèi)存或硬盤(pán)中再讀取數(shù)據(jù)、指令,從而提高運(yùn)行速度。

CPU緩存分為3級(jí):L1一級(jí)緩存、L2二級(jí)緩存、L3三級(jí)緩存,它們的作用都是作為CPU與主內(nèi)存之間的高速數(shù)據(jù)緩沖區(qū),L1最靠近CPU核心;L2其次;L3再次。

CPU

核心1

核心2

L1

L1

L1

數(shù)據(jù)緩存

指令緩存

數(shù)據(jù)緩存

指令緩存

L2緩存

L2緩存

L3緩存

速度方面:L1最快、L2次快、L3最慢;

大小方面:L1最小、L2較大、L3最大。

LO:

寄存器

CPU寄存器保存著從高速

更小

緩存存儲(chǔ)器取出的字

LI

更快和

LI:

高速緩存

(每字節(jié))

(SRAM)

LI高速緩存保存著從L.2

成本更高的

高速緩存取出的緩存行

L2

L.2:

存儲(chǔ)設(shè)備

高速緩存

L2高速緩存保存著從L3

SRAM)

高速緩存取出的級(jí)存行

L3

L3:

高速緩存

(SRAM)

L3高速緩存保存著從主存

高速緩存取出的緩存行

更大

LA:

主存(DRAM)

更慢和

主存保存著從本地磁盤(pán)

(每字節(jié))

取出的磁盤(pán)塊

差別更低的

L5

本地二級(jí)存儲(chǔ)(本地磁盤(pán))

存儲(chǔ)設(shè)備

本地磁盤(pán)保存著從遠(yuǎn)程網(wǎng)絡(luò)

服務(wù)器磁盤(pán)上取出的文件

遠(yuǎn)程二級(jí)存儲(chǔ)

L6:

(分布式文件系統(tǒng),Wcb服務(wù)器)

CPU會(huì)先在L1中尋找需要的數(shù)據(jù),找不到再去L2,還找不到再去L3,L3都沒(méi)有那就只能去主內(nèi)存找了。

一級(jí)緩存其實(shí)還分為一級(jí)數(shù)據(jù)緩存(Data Cache,L1d-Cache)和一級(jí)指令緩存(Instruction Cache,l1i-Cache),分別用于存放數(shù)據(jù)及指令,兩者可同時(shí)被CPU訪問(wèn),減少了CPU多核心、多線程爭(zhēng)用緩存造成的沖突,提高處理器性能。

緩存行(Cache Line)

CPU從主內(nèi)存中加載數(shù)據(jù)到緩存,是以行為單位的,一行64字節(jié),CPU每次從主存中拉取數(shù)據(jù)時(shí),會(huì)把相鄰的數(shù)據(jù)也存入同一個(gè)cache line。也就是說(shuō),如果CPU計(jì)算時(shí)需要用到變量a,假設(shè)a是long類(lèi)型,8字節(jié),那么CPU會(huì)把a(bǔ)左右挨著的變量共64字節(jié),統(tǒng)統(tǒng)拿過(guò)去。

CACHE LINE

CPU

CACHELINE

CPU

緩存

CACHE LINE

上面代碼加了volatile后,速度變慢的原因分析

數(shù)組是一塊連續(xù)內(nèi)存,并且我們的數(shù)組有兩個(gè)Demo對(duì)象,一共16字節(jié),所以兩個(gè)線程使用加載時(shí),極有可能在同一cache line,,都在自己的工作內(nèi)存中有兩個(gè)Demo對(duì)象的副本,雖然線程1操作arr[0],線程2操作arr[1],看似互不影響,但是由于變量都是volatile的,當(dāng)線程1修改a變量時(shí),線程2中的arr[0].a就無(wú)效了,這就導(dǎo)致整個(gè)cache line無(wú)效,所以每次都要從主內(nèi)存中取數(shù)據(jù)。

內(nèi)存

arr

Demo1(a)

Demo2(a)

線程1

線程2

工作內(nèi)存

工作內(nèi)存

Demo2a)

Demoia)

Demo2(a)

Demo1a)

對(duì)代碼進(jìn)行修改:

這樣變量代碼中數(shù)據(jù)的兩個(gè)對(duì)象,就不可能在同一cache line,所以兩個(gè)線程互不影響,再次運(yùn)行:700毫秒左右。

著名的disruptor框架,也是聲明很多l(xiāng)ong類(lèi)型變量:

kagecom.1maxdisupto

importcom.1max.disupto.odclyp

PBNAINBUERXTENDRINGBUEERFLEIDBE

publicstaticTALC-

protectedlongpl;

1ongP2;

Protected

ProtectedlongP3:

ProtectedlongP4;

protectedlongp5;

protectedlongp6;

ProtectedlongP7;

packagecom.1max.disruptori

abstractClassRingBuferPad

Protectedlongpl;

protectedlongP2;

ProtectedlongP3:

protectedlongp4;

Protectedlongp5;

protectedlongp6;

protectedlongP7;

RingBuFferpado

2. 有序性

我們都知道java代碼是逐句執(zhí)行的,看以下代碼:

上面代碼,code1在code2之前,但是JVM在真正執(zhí)行這段代碼的時(shí)候并不保證code1一定會(huì)在code2前面執(zhí)行,因?yàn)檫@里可能會(huì)發(fā)生指令重排序。

以下僅做了解:

計(jì)算機(jī)在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令重排:

源代碼 -> 編譯器優(yōu)化的重排 -> 指令并行的重排 -> 內(nèi)存系統(tǒng)的重排 -> 最終執(zhí)行指令

●編譯器優(yōu)化的重排序:編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序。

●指令級(jí)并行的重排序:現(xiàn)代處理器采用了指令級(jí)并行技術(shù)來(lái)將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴(lài)性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。

●內(nèi)存系統(tǒng)的重排序:由于處理器使用緩存和讀/寫(xiě)緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

雖然不能保證執(zhí)行順序跟代碼順序一致,但它保證單線程下程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。以上代碼,code1和code2哪個(gè)先執(zhí)行并不影響整個(gè)方法的執(zhí)行結(jié)果。

在Java內(nèi)存模型中,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過(guò)程不會(huì)影響到單線程的執(zhí)行結(jié)果,卻會(huì)影響到多線程并發(fā)執(zhí)行的結(jié)果。

在多線程情況下:

可以通過(guò)synchronized和Lock來(lái)保證有序性,也可以通過(guò)volatile關(guān)鍵字來(lái)保證一定的“有序性”。

單例----DCL(Double Check Lock)

以上代碼,在高并發(fā)情況先就有可能產(chǎn)生問(wèn)題。問(wèn)題在于:instance = new Singleton();這句代碼。

這條語(yǔ)句實(shí)際上包含了三條指令:

memory =allocate(); //1:分配對(duì)象的內(nèi)存空間

ctorInstance(memory); //2:初始化對(duì)象

instance =memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址

其中2、3的執(zhí)行順序是可以互換的。

線程B

線程A

1.分配對(duì)象的

內(nèi)存空間

判斷instance是

3.設(shè)置instance

否為null

指向內(nèi)存空間

線程B初次訪問(wèn)

對(duì)象

2.初始化對(duì)象

用volatile修飾instance變量,就可以禁止2和3重排序。

為什么volatile可以禁止指令重排序?

通過(guò)提供“內(nèi)存屏障”的方式來(lái)防止指令被重排序,為了實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障。

內(nèi)存屏障的概念,不用理解,可以不用看

內(nèi)存屏障:針對(duì)跨處理器的讀寫(xiě)操作,它被插入到兩個(gè)指令之間,作用是禁止編譯器和處理器重排序

按可見(jiàn)性可以分為兩類(lèi):加載屏障(Load Barrier)和存儲(chǔ)屏障(Store Barrier)。

加載屏障:刷新處理器緩存。

存儲(chǔ)屏障:沖刷處理器緩存。

JVM會(huì)在MonitorEnter(申請(qǐng)鎖)對(duì)應(yīng)的機(jī)器碼指令后面,臨界區(qū)代碼開(kāi)始之前插入Load Barrier,以達(dá)到臨界區(qū)內(nèi)部使用的共享變量都是新值的作用。同樣,會(huì)在MonitorExit(釋放鎖)對(duì)應(yīng)的指令后插入Store Barrier,以達(dá)到臨界區(qū)對(duì)共享變量的更改及時(shí)寫(xiě)回主存。

按有序性劃分:分為獲取屏障(Acquire Barrier)和釋放屏障(Release Barrier)。

獲取屏障:在一個(gè)讀操作之后插入一個(gè)屏障,禁止與之后的任何讀寫(xiě)操作重排

釋放屏障:在一個(gè)寫(xiě)操作之前插入一個(gè)屏障,禁止與其前面任何讀寫(xiě)操作重排

內(nèi)存屏障有4種:

LoadLoad屏障:對(duì)于這樣的語(yǔ)句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問(wèn)前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

StoreStore屏障:對(duì)于這樣的語(yǔ)句Store1; StoreStore; Store2,在Store2及后續(xù)寫(xiě)入操作執(zhí)行前,保證Store1的寫(xiě)入操作對(duì)其它處理器可見(jiàn)。

LoadStore屏障:對(duì)于這樣的語(yǔ)句Load1; LoadStore; Store2,在Store2及后續(xù)寫(xiě)入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

StoreLoad屏障:對(duì)于這樣的語(yǔ)句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫(xiě)入對(duì)所有處理器可見(jiàn)。它的開(kāi)銷(xiāo)是四種屏障中最大的。在大多數(shù)處理器的實(shí)現(xiàn)中,這個(gè)屏障是個(gè)萬(wàn)能屏障,兼具其它三種內(nèi)存屏障的功能

volatile的內(nèi)存屏障策略非常嚴(yán)格保守,非常悲觀且毫無(wú)安全感的心態(tài):

在每個(gè)volatile寫(xiě)操作前插入StoreStore屏障,在寫(xiě)操作后插入StoreLoad屏障;在每個(gè)volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障;

由于內(nèi)存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實(shí)現(xiàn)了通信,使得volatile表現(xiàn)出了鎖的特性。

但并不是所有代碼都能重排序:

以上代碼,code3和code4的執(zhí)行順序就不能互換。因?yàn)橹嘏判驎r(shí)是會(huì)考慮指令之間的數(shù)據(jù)依賴(lài)性。

什么是happen-before(這個(gè)概念沒(méi)什么不用看)

JMM可以通過(guò)happens-before關(guān)系向程序員提供跨線程的內(nèi)存可見(jiàn)性保證(如果A線程的寫(xiě)操作a與B線程的讀操作b之間存在happens-before關(guān)系,盡管a操作和b操作在不同的線程中執(zhí)行,但JMM向程序員保證a操作將對(duì)b操作可見(jiàn))。

具體的定義為:

1)如果一個(gè)操作happens-before另一個(gè)依賴(lài)操作,那么第一個(gè)操作的執(zhí)行結(jié)果將對(duì)第二個(gè)操作可見(jiàn),而且第一個(gè)操作的執(zhí)行順序排在第二個(gè)操作之前。

2)兩個(gè)操作之間存在happens-before關(guān)系,并不意味著Java平臺(tái)的具體實(shí)現(xiàn)必須要按照happens-before關(guān)系指定的順序來(lái)執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關(guān)系來(lái)執(zhí)行的結(jié)果一致,那么這種重排序并不非法(也就是說(shuō),JMM允許這種重排序)。

具體的規(guī)則:

(1)程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中的任意后續(xù)操作。

(2)監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖。

(3)volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě),happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀。

(4)傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。

(5)start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動(dòng)線程B),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。

(6)Join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。

(7)程序中斷規(guī)則:對(duì)線程interrupted()方法的調(diào)用先行于被中斷線程的代碼檢測(cè)到中斷時(shí)間的發(fā)生。

(8)對(duì)象finalize規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行于發(fā)生它的finalize()方法的開(kāi)始。

3. 原子性

程序的原子性指整個(gè)程序中的所有操作,要么全部完成,要么全部不完成,不可能停滯在中間某個(gè)環(huán)節(jié),有著“同生共死”的感覺(jué)。對(duì)線程而言,一個(gè)操作一旦開(kāi)始,就不會(huì)被其他線程所干擾。

java中必須借助于synchronized、Lock、鎖等,來(lái)保證整塊代碼的原子性

3. CAS

流程圖

下面的2,3這兩步,對(duì)于操作系統(tǒng)來(lái)說(shuō)是一條指令

不同

A.INCREMENTANDGET();中有CAS操作

是原子操作,不可被打斷

3.NEWVAL+1

1.從內(nèi)存獲取A的值:NEWVAL

2.NEWVAL 跟A比較

A.INCREMENTANDGET();

相同

第2步,中如果判斷出不等,重新走第1步,這個(gè)過(guò)程叫做自旋

所以,CAS也稱(chēng)為自旋鎖

壞處:如果大量線程同時(shí)修改一個(gè)變量,會(huì)導(dǎo)致很多線程不停的自

旋,自旋很占用CPU,這樣性能不好

LongAdder

流程圖

newLongAddero:

LongAddera

base

客戶端

Cell數(shù)組,初始長(zhǎng)度為2,每次擴(kuò)容都是變?yōu)樵瓉?lái)的2倍

cell

cell

自動(dòng)分段遷移:

如果某個(gè)cell執(zhí)行CAS失敗了,那么就會(huì)自動(dòng)去找另外一個(gè)Cel進(jìn)行CAS操作

ABA問(wèn)題:

int a =1;

1. 線程1 獲取a的最新值后,準(zhǔn)備CAS操作,但這時(shí)線程暫停

2. 線程2 修改a的值為2

3. 線程3 又修改a的值為1

4. 線程1 繼續(xù)運(yùn)行,修改a的值。

這種情況下,線程1 雖然可以修改成功,但是這個(gè)a已經(jīng)不是最初的a了,中間經(jīng)歷的一些變化,如果修改成功,可能

導(dǎo)致一些問(wèn)題,這就是ABA問(wèn)題。

解決:加時(shí)間戳或版本號(hào)

ABA問(wèn)題:

inta-1:

1.線程1獲取a的最新值后,準(zhǔn)備CAS操作,但這時(shí)線程暫停

2.線程2修改a的值為2

3.線程3又修改a的值為1

4.線程1繼續(xù)運(yùn)行,修改a的值.

這種情況下,線程1雖然可以修改成功,但是這個(gè)已經(jīng)不是最初的a了,中間經(jīng)歷的一些變化,

如果修改成功,可能導(dǎo)致一些問(wèn)題,這就是ABA問(wèn)題.

解決:加時(shí)間蜜或版本號(hào)

a三2

a三1

a三1

a三1

CASa-1

CAS

CASa-2

線程1

線程3

線程2

4. Lock

1. 基礎(chǔ)概念

2. 基礎(chǔ)方法

Condition

3. 公平鎖和非公平鎖

4. ReadWriteLock

4. ReentrantLock 原理

AQS AbstractQueuedSynchronizer

抽象隊(duì)列同步器:AQS內(nèi)部有一個(gè)核心的變量state,int類(lèi)型,代表加鎖的狀態(tài)。初始值是0。還有一個(gè)關(guān)鍵變量thread,用來(lái)記錄當(dāng)前加鎖的是哪個(gè)線程,初始值是null

過(guò)程:

線程a 調(diào)用lock()方法進(jìn)行加鎖,就是用CAS將state值從0變?yōu)?,然后設(shè)置 thread=線程a

ReentrantLock是一個(gè)可重入鎖,每次線程a 再次加鎖就是把state的值給累加 1,別的沒(méi)啥變化

這時(shí),線程b 進(jìn)來(lái)發(fā)現(xiàn)state已經(jīng)不是0了,且“加鎖線程”不是自己,所以加鎖失敗。

線程b 會(huì)將自己放入AQS中的一個(gè)等待隊(duì)列,等線程a 釋放鎖之后,重新嘗試加鎖

線程a 在執(zhí)行完自己的業(yè)務(wù)邏輯代碼之后,就會(huì)釋放鎖!

就是將state變量的值遞減1,等state值為0,則徹底釋放鎖,會(huì)將“加鎖線程”變量也設(shè)置為null

AQS:內(nèi)部定義了獲取和釋放鎖的抽象方法,由子類(lèi)具體實(shí)現(xiàn),FairSync和NonfairSync都實(shí)現(xiàn)了這倆方法,這是模板方法模式

protectedbooleantrycquire(intarg)

wUNsupportedoperationException

throw

newUn

**

北ate

the

etoreflectareleaseinexclusive

Attemptstoset

mode.

Thismethodisawaysvkdhhdmng

*

*

p>Thedefaultimplementationthrows

(@linkUnsupportedoperationException

*

*

*

paramargthereleaserqumentTahe

*

passedtoareleasemethodhueuu

entrytoaconditionwaitus

*

uninterpretedandcanrepresentanythingyuke

*

codetruelifthisobjectisnowiu

ereturn

*

statesothatanymtinh

andt@codefalseotherwise.

*

@throws

II1egaiMonitorstateExceptioni

onifreleasingwouldplacethis

*

*

synchronzerinanegstem

*

thzowninaconsstentashionorsynchonztion

correctly.

*

*

unsupportedoperationxceptionu

@throws

protectedbooleantryRelease(itrg)

thrownewUnsupportedoperationxcetin

1. AQS內(nèi)部變量

AQS中等待隊(duì)列是先進(jìn)先出,本質(zhì)是鏈表,下面變量都在:AbstractQueuedSynchronizer

2. ReentrantLock 源碼

addWaiter:其實(shí)就是將線程放入同步隊(duì)列

acquireQueued

3. ReentrantLock 流程

加鎖

CAS加鎖

開(kāi)始

入隊(duì)

加鎖線程是否是本線程

state--0

失敗

獲取state

成功

設(shè)置exclusiveOynerThread為本線程

結(jié)束

state+1

入隊(duì)

1.利用CAS設(shè)置head-newNodeo

2.tail-head

tail-null

開(kāi)始

tailanull

eng

3.繼續(xù)下一次循環(huán)

入隊(duì)

1.當(dāng)前線程節(jié)點(diǎn)(node)的prev屬性指向pred(其實(shí)就是tail)

2.利用CAS讓tailnode

3.讓之前尾節(jié)點(diǎn)的next屬性指向node

阻塞

1.判斷當(dāng)前node的prev是不是head

需要注意:

只有設(shè)置當(dāng)前node的prev的waitstatus--1

2.執(zhí)行tryAcquire

當(dāng)前node才會(huì)阻塞

開(kāi)始

嘗試獲取鎖

當(dāng)前nod阻寨

失敗

成功

1.設(shè)置head-當(dāng)前node

2.設(shè)置之前head的next-null

5. synchronized 和 Lock區(qū)別

4. 其他同步方式

1. CountDownLatch

2. CyclicBarrier

3. Semaphore

5. ThreadLocal

1. 簡(jiǎn)單使用

2. 使用場(chǎng)景

3. ThreadLocalMap結(jié)構(gòu)

ThreadLocal 本質(zhì)是操作當(dāng)前線程的 theadlocals 屬性,它是 ThreadLocalMap 類(lèi)型

ThreadLocalMap結(jié)構(gòu):存儲(chǔ) Entry 數(shù)組,數(shù)組中的元素有兩個(gè)屬性:referent、value

●referent :是 ThreadLocal 對(duì)象

●value:自己調(diào)用 ThreadLocal 的 set 方法,設(shè)置的值

Threadlocal本質(zhì)操作的是當(dāng)前線程對(duì)象的threadLocals屬性

threadLocals是ThreadLocalmap類(lèi)型

Entry數(shù)組

referent:ThreadLocal對(duì)象

referent:Threadlocal對(duì)象

value:自己設(shè)置的值

value:自己設(shè)置的值

TheadLocal跟threadLocals的關(guān)系

12

ThreadLocal0:

staticThreadLocal

myThreadLocalznew

13

plicstaticvodmin

15

Threadt-newThread(0->

16

yThreadLocal.set("feng"//這時(shí),"eng"跟當(dāng)

"跟當(dāng)前線程關(guān)聯(lián),屬于當(dāng)前線程的私有變量,只能在當(dāng)前線程中獲取到

17

18

"+myThreadLocal.getO);

System.out.printin(Ted

getName

ThreadLocalo1main020-21.

工山川貓美

Variables

oOThread.currentThreado-hread@657)"ThreadThread-0.main

staticmembersofThreadLocalo1

omyThreadLocalEThreadLocal@672

這時(shí)候可以看到myThreadLocal變量指向的對(duì)象是672

查看當(dāng)前線程的threadLocals屬性

Variables

Thread.currentThreadohread@657)"ThreadThread-0.5mai

nameBTnreao-U

fpriority-5

fthreadQ-null

eetoP-528824320

single_step-false

daemon-false

00

stillborn-false

ftarC

targethreadLocaloislambda@674

group三fThreadGoup@658Javalang.hdupeminmxpr1

Pade

contextClassLoaderLaunchesAppclassod@659

inheritedAccessContolcontextccesscontlconet@7

threadLocals二ThreadLocalsThreadLocalMap@67)

table二ThreadLocalsThreadLocalmapsEntry(1@79

Notshowingnullelements

10-ThreadLocalsThreadLocalMapsentry@680)

fvalue-"feng"

freferent-ThreadLocal@672)

queue-ReferenceQueuesNull@682

nextanull

discovered二null

SIZeBT

threshold-10

inheritableThreadLocals二null

starkSize80

EventLo

Build

可以看到,最里層有個(gè)referent屬性也是指向了672

其實(shí)set本質(zhì):把myThreadLocal對(duì)象和feng,封裝成一個(gè)Entry對(duì)象,然后放到當(dāng)前線程的threadLocals屬性中

4. 原理

5. 引用類(lèi)型

6. 內(nèi)存泄漏

把myThreadLocal設(shè)置為null后,可以看到referent沒(méi)有什么變化

ThreadHelper.slegp(ms:2000)

19

20

wyThreadLocai-nuli;

21

System.gc):

22

System.out.printin(myThreadLoca1)

23

):

24

t.start0:

ThreadLocal01main020->1..9

廣山業(yè)園美

Variables

threadLocals二ChreadLocalsThreadLocalMap@677

table二ChreadLocalsThreadlocalMapsentry[16]@680)

Notshowingnullelements

10-ThreadLocalsThreadLocalMapsentry@681

value-"feng"

reterentThreadLocal@683

queyeRReferenceQueue$Null@684

nextanull

discovered二null

size二1

threshold-10

finheritableThreadLocalsenull

但是在執(zhí)行 System.gc(); 這句代碼后

20

myThreadLocaionuil;

System.gcO:

System.out.printn(ThreadLoca

):

tstart0:

ThreadLocalo1)main0>0~>f..

身當(dāng)

Variables

THreadLocalsThreadLocalMap@677)

threadLocals

ftable二CThreadLocalSsThreadlocalMapsentry[16]@680!

Notshowingnullelements

10-ChreadLocalsThreadLocalMapsEntry@681)

value二"feng"

referent二null

queue-ReferenceQueuesNull@684)

nextThreadLocalsThreadLocalMapsEntry@681

discovered-null

fsize-1

fthreshold-10

inheritableThreadLocals二null

stacksize30

nativeParkEventPointer二0

referent變成null,那么這時(shí)候,feng這個(gè)值,就再也拿不到了

當(dāng)然,當(dāng)前線程結(jié)束后feng這個(gè)值還是會(huì)被回收的

6. 阻塞隊(duì)列

1. 整體介紹

Thread2

Thread1

BlockingQueue

Put

Take

一個(gè)BlockingQueue,其中一個(gè)線程放入其中,另一個(gè)線程從中取出.

https://blog.csdn.neuiengxiaoshi

2. ArrayBlockingQueue

3. LinkedBlockingQueue

4. PriorityBlockingQueue

5. DelayQueue

Java復(fù)制代碼

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

/**

* DelayQueue

* 延遲獲取的無(wú)界隊(duì)列,添加的元素必須實(shí)現(xiàn) Delayed 接口

* 在增加元素時(shí),可以指定一個(gè)時(shí)間,只有到期后后才能從隊(duì)列中獲取元素。

*/

classDelayDemoimplementsDelayed{

longdelayTime;//過(guò)期時(shí)間

longtime;//多少秒過(guò)期

publicDelayDemo(inttime){

this.time=time;

// 如果time=3,System.currentTimeMillis()= 1616056291000 ,那么就是 1616056294000 時(shí)過(guò)期

this.delayTime=time*1000+System.currentTimeMillis();

}

/**

* 返回還有多久到期,DelayQueue隊(duì)列內(nèi)部會(huì)不停的調(diào)用這個(gè)方法

* @param unit

* @return 返回值<=0 表示到期了

*/

@Override

publiclonggetDelay(TimeUnitunit){

returndelayTime-System.currentTimeMillis();

}

/**

* 排序使用,過(guò)期時(shí)間短的排到前面

* @param o

* @return

*/

@Override

publicintcompareTo(Delayedo){

return(int)(this.getDelay(TimeUnit.SECONDS)-o.getDelay(TimeUnit.SECONDS));

}

@Override

publicStringtoString(){

return"DelayDemo{"+

"delayTime="+delayTime+

", time="+time+

'}';

}

}

publicclassQueue05_DelayQueue{

publicstaticvoidmain(String[]args)throwsInterruptedException{

DelayQueue<DelayDemo> queue=newDelayQueue();

queue.add(newDelayDemo(3));

queue.add(newDelayDemo(2));

queue.add(newDelayDemo(5));

System.out.println(queue.take());

System.out.println(queue.take());

System.out.println(queue.take());

}

}

6. SynchronousQueue

Java復(fù)制代碼

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

/**

* SynchronousQueue

* 長(zhǎng)度為0的阻塞隊(duì)列,每一個(gè)put操作會(huì)阻塞,直到另一個(gè)take操作

* 線程池中 newCachedThreadPool 用到這個(gè)隊(duì)列

*

*/

publicclassQueue06_SynchronousQueue{

publicstaticvoidmain(String[]args)throwsInterruptedException{

SynchronousQueue<String> queue=newSynchronousQueue();

newThread(() -> {

try{

System.out.println("put 1 。。。。。。");

queue.put("1");

System.out.println("put 2 。。。。。。");

queue.put("2");

}catch(InterruptedExceptione){

e.printStackTrace();

}

}).start();

newThread(() -> {

try{

ThreadHelper.sleep(3000);

System.out.println("take "+queue.take()+" 。。。。。。");

ThreadHelper.sleep(3000);

System.out.println("take "+queue.take()+" 。。。。。。");

}catch(InterruptedExceptione){

e.printStackTrace();

}

}).start();

}

}

總結(jié)

以上是生活随笔為你收集整理的多线程进阶的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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