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

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

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java线程安全以及线程安全的实现方式和内存模型(JMM)

發(fā)布時(shí)間:2023/12/4 java 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java线程安全以及线程安全的实现方式和内存模型(JMM) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、了解幾個(gè)概念?

1)臨界區(qū):

?臨界區(qū)指的是一個(gè)訪問(wèn)共用資源(例如:共用設(shè)備或是共用存儲(chǔ)器)的程序片段,而這些共用資源又無(wú)法同時(shí)被多個(gè)線程訪問(wèn)的特性。當(dāng)有線程進(jìn)入臨界區(qū)段時(shí),其他線程或是進(jìn)程必須等待,有一些同步的機(jī)制必須在臨界區(qū)段的進(jìn)入點(diǎn)與離開(kāi)點(diǎn)實(shí)現(xiàn),以確保這些共用資源是被互斥獲得使用?

?

2)互斥量:

互斥量是一個(gè)可以處于兩態(tài)之一的變量:解鎖和加鎖。這樣,只需要一個(gè)二進(jìn)制位表示它,不過(guò)實(shí)際上,常常使用一個(gè)整型量,0表示解鎖,而其他所有的值則表示加鎖。互斥量使用兩個(gè)過(guò)程。當(dāng)一個(gè)線程(或進(jìn)程)需要訪問(wèn)臨界區(qū)時(shí),它調(diào)用mutex_lock。如果該互斥量當(dāng)前是解鎖的(即臨界區(qū)可用),此調(diào)用成功,調(diào)用線程可以自由進(jìn)入該臨界區(qū)。
另一方面,如果該互斥量已經(jīng)加鎖,調(diào)用線程被阻塞,直到在臨界區(qū)中的線程完成并調(diào)用mutex_unlock。如果多個(gè)線程被阻塞在該互斥量上,將隨機(jī)選擇一個(gè)線程并允許它獲得鎖。?

?

3)信號(hào)量:

信號(hào)量(Semaphore),有時(shí)被稱為信號(hào)燈,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來(lái)保證兩個(gè)或多個(gè)關(guān)鍵代碼段不被并發(fā)調(diào)用。在進(jìn)入一個(gè)關(guān)鍵代碼段之前,線程必須獲取一個(gè)信號(hào)量;一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號(hào)量。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待直到第一個(gè)線程釋放信號(hào)量。為了完成這個(gè)過(guò)程,需要?jiǎng)?chuàng)建一個(gè)信號(hào)量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個(gè)關(guān)鍵代碼段的首末端。確認(rèn)這些信號(hào)量VI引用的是初始創(chuàng)建的信號(hào)量。?

?

4)CAS操作(Compare-and-Swap):

CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B

?

5)重排序:

編譯器和處理器”為了提高性能,而在程序執(zhí)行時(shí)會(huì)對(duì)程序進(jìn)行的重排序。它的出現(xiàn)是為了提高程序的并發(fā)度,從而提高性能!但是對(duì)于多線程程序,重排序可能會(huì)導(dǎo)致程序執(zhí)行的結(jié)果不是我們需要的結(jié)果!重排序分為“編譯器”和“處理器”兩個(gè)方面,而“處理器”重排序又包括“指令級(jí)重排序”和“內(nèi)存的重排序”

?

?

?

?

二、Java內(nèi)存模型(JMM)

線程與內(nèi)存交互操作如下?

所有的變量(實(shí)例字段,靜態(tài)字段,構(gòu)成數(shù)組對(duì)象的 元素,不包括局部變量和方法參數(shù))都存儲(chǔ)在主內(nèi)存中,每個(gè)線程有自己的工作內(nèi)存,線程的工作內(nèi)存保存被線程使用到變量的主內(nèi)存副本拷貝線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存的變量。不同線程之間也不能直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞通過(guò)主內(nèi)存來(lái)完成。
?

1、Java內(nèi)存模型定義了八種操作

lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一個(gè)線程獨(dú)占的狀態(tài); unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定; read(讀取):作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)魉偷骄€程中的工作內(nèi)存,以便隨后的load動(dòng)作使用; load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中; use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎; assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存中的變量; store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作; write(寫(xiě)入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值寫(xiě)入主內(nèi)存的變量中。

2、volatile關(guān)鍵字作用:

1)、當(dāng)寫(xiě)一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量值立即刷新到主內(nèi)存中。
2)、當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存設(shè)置為無(wú)效,直接從主內(nèi)存中讀取共享變量。
3)、禁止指令重排序優(yōu)化
4)、volatile關(guān)鍵字不能保證在多線程環(huán)境下對(duì)共享數(shù)據(jù)的操作的正確性。可以使用在自己狀態(tài)改變之后需要立即通知所有線程的情況下,也就是說(shuō)volatile不能保證線程同步,Java語(yǔ)言提供了一種稍弱的同步機(jī)制,即volatile變量,用來(lái)確保將變量的更新操作通知到其他線程,這就是所謂的線程可見(jiàn)性,當(dāng)把變量聲明為volatile類型后,編譯器與運(yùn)行時(shí)都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見(jiàn)的地方,因此在讀取volatile類型的變量時(shí)總會(huì)返回最新寫(xiě)入的值,所以volatile具有可見(jiàn)性。對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫(xiě)入,然后synchronized也是具有可見(jiàn)性。

5)、volatile的原子性:對(duì)任意單個(gè)volatile變量的讀/寫(xiě)具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性,所以volatile能保證可見(jiàn)性,不能保證原子性。

?

理解volatile特性的一個(gè)好方法是:把對(duì)volatile變量的單個(gè)讀/寫(xiě),看成是使用同一個(gè)監(jiān)視器鎖對(duì)這些單個(gè)讀/寫(xiě)操作做了同步

class VolatileFeaturesExample {volatile long vl = 0L; //使用volatile聲明64位的long型變量public void set(long l) {vl = l; //單個(gè)volatile變量的寫(xiě)}public void getAndIncrement () {vl++; //復(fù)合(多個(gè))volatile變量的讀/寫(xiě)}public long get() {return vl; //單個(gè)volatile變量的讀} }

假設(shè)有多個(gè)線程分別調(diào)用上面程序的三個(gè)方法,這個(gè)程序在語(yǔ)意上和下面程序等價(jià):

class VolatileFeaturesExample {long vl = 0L; // 64位的long型普通變量public synchronized void set(long l) { //對(duì)單個(gè)的普通 變量的寫(xiě)用同一個(gè)監(jiān)視器同步vl = l;}public void getAndIncrement () { //普通方法調(diào)用long temp = get(); //調(diào)用已同步的讀方法temp += 1L; //普通寫(xiě)操作set(temp); //調(diào)用已同步的寫(xiě)方法}public synchronized long get() { //對(duì)單個(gè)的普通變量的讀用同一個(gè)監(jiān)視器同步return vl;} }

3 volatile和synchronized區(qū)別?
1)、volatile僅能使用在變量級(jí)別;synchronized則可以使用在變量、方法和類級(jí)別;

2)、volatile讀數(shù)據(jù)直接從主存中讀取;synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問(wèn)該變量,其他線程被阻塞住。

3)、volatile能保證可見(jiàn)性,不能保證原子性;而synchronized則可以保證可見(jiàn)性和原子性;

4)、volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞。

5)、volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化進(jìn)行指令重排列;synchronized標(biāo)記的變量可以被編譯器優(yōu)化進(jìn)行指令重排列。

?

?

三、JMM特性

1)、原子性

就是指該操作是不可再分的。不論是多核還是單核,具有原子性的量,同一時(shí)刻只能有一個(gè)線程來(lái)對(duì)它進(jìn)行操作。簡(jiǎn)而言之,在整個(gè)操作過(guò)程中不會(huì)被線程調(diào)度器中斷的操作,都可認(rèn)為是原子性。比如 a = 1;

?

2)、可見(jiàn)性

可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改

?

3)、?有序性

Java內(nèi)存模型中有序性可歸納為這樣一句話:如果在本線程內(nèi)觀察,所有操作都是有序的,如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無(wú)序的

是指對(duì)于單線程的執(zhí)行代碼,執(zhí)行是按順序依次進(jìn)行的。但在多線程環(huán)境中,則可能出現(xiàn)亂序現(xiàn)象,因?yàn)樵诰幾g過(guò)程會(huì)出現(xiàn)“指令重排”,重排后的指令與原指令的順序未必一致

?


四、java中的線程安全等級(jí)

不可變
可以是基本類型的final;可以是final對(duì)象,但對(duì)象的行為不會(huì)對(duì)其狀態(tài)產(chǎn)生任何影響,比如String的subString就是new一個(gè)String對(duì)象各種Number類型如BigInteger和BigDecimal等大數(shù)據(jù)類型都是不可變的,但是同為Number子類型的AtomicInteger和AtomicLong則并非不可變。原因與它里面狀態(tài)對(duì)象是unsafe對(duì)象有關(guān),所做的操作都是CAS操作,可以保證原子性。

絕對(duì)線程安全
不管運(yùn)行時(shí)環(huán)境如何,調(diào)用者都不需要任何額外的同步措施。

相對(duì)線程安全
這是我們通常意義上的線程安全。需要保證對(duì)象單獨(dú)的操作是線程安全的。比如Vector,HashTable,synchronizedCollection包裝集合等。

線程兼容
對(duì)象本身不是線程安全的,但可以通過(guò)同步手段實(shí)現(xiàn)。一般我們說(shuō)的不是線程安全的,絕大多數(shù)是指這個(gè)。比如ArrayList,HashMap等。

線程對(duì)立
不管調(diào)用端是否采用了同步的措施,都無(wú)法在并發(fā)中使用的代碼。

?

?

?

五、線程安全的實(shí)現(xiàn)方式

要實(shí)現(xiàn)線程安全一般至少需要兩個(gè)特性:原子性和可見(jiàn)性

1)使用synchronize:它本具有原子性和可見(jiàn)性的,所以如果使用了synchronize修飾的操作,那么就自帶了可見(jiàn)性,synchronized使用悲觀鎖來(lái)實(shí)現(xiàn)線程安全

2)使用原子類代替基本數(shù)據(jù)類型,原子類是使用樂(lè)觀鎖來(lái)實(shí)現(xiàn)線程安全,多線程環(huán)境下執(zhí)行a++,可以使用AtomicInteger類incrementAndGet()方法實(shí)現(xiàn),同樣是使用了volatile來(lái)保證可見(jiàn)性;使用Unsafe調(diào)用native本地方法CAS,CAS采用總線加鎖或緩存加鎖方式來(lái)保證原子性。

3 )?使用volatile關(guān)鍵字,volatile不一定就有原子性,比如用volatile修飾的變量進(jìn)行++或者--操作(num++),我們需要讓volatile修飾的變量需要具有原子性,那么我們一般可以設(shè)置在boolean類型變量上,如下

volatile boolean tag = true; 線程1 while(tag){}; 線程2 while(tag){};

如果有變量自增或者自減,我們可以使用原子類(AtomicInteger)

4)使用ThreadLocal對(duì)各個(gè)線程進(jìn)行隔離

可以參考我的這篇博客 :Java之ThreaLocal

5)我們還可以用其他的鎖,比如重入鎖(ReentrantLock) 保證線程安全

6)我們還可以用 臨界區(qū)互斥量、信號(hào)量 保證線程安全

?

參考文獻(xiàn):

https://developer.51cto.com/art/201910/605093.htm

https://www.open-open.com/lib/view/open1459412319988.html

https://www.iteye.com/blog/smallbug-vip-2275743

https://juejin.im/post/5c936018f265da60ec281bcb

https://blog.csdn.net/jingzi123456789/article/details/78004074

總結(jié)

以上是生活随笔為你收集整理的Java线程安全以及线程安全的实现方式和内存模型(JMM)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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