一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務(wù)器之家 - 編程語言 - JAVA教程 - 深入探討Java多線程中的volatile變量

深入探討Java多線程中的volatile變量

2020-04-07 11:25lijiao JAVA教程

這篇文章主要為大家詳細并深入的探討Java多線程中的volatile變量,volatile用來確保將變量的更新操作通知到其他線程,保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新,感興趣的小伙伴們可以參考一下

volatile 變量提供了線程的可見性,并不能保證線程安全性和原子性。

什么是線程的可見性:

  鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現(xiàn)對共享數(shù)據(jù)的協(xié)調(diào)訪問協(xié)議,這樣,一次就只有一個線程能夠使用該共享數(shù)據(jù)。可見性要更加復(fù)雜一些,它必須確保釋放鎖之前對共享數(shù)據(jù)做出的更改對于隨后獲得該鎖的另一個線程是可見的 -- 如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發(fā)許多嚴重問題。

具體看volatile的語義

  volatile相當(dāng)于synchronized的弱實現(xiàn),也就是說volatile實現(xiàn)了類似synchronized的語義,卻又沒有鎖機制。它確保對volatile字段的更新以可預(yù)見的方式告知其他的線程。

  volatile包含以下語義:

  (1)Java 存儲模型不會對valatile指令的操作進行重排序:這個保證對volatile變量的操作時按照指令的出現(xiàn)順序執(zhí)行的。

  (2)volatile變量不會被緩存在寄存器中(只有擁有線程可見)或者其他對CPU不可見的地方,每次總是從主存中讀取volatile變量的結(jié)果。也就是說對于volatile變量的修改,其它線程總是可見的,并且不是使用自己線程棧內(nèi)部的變量。也就是在happens-before法則中,對一個valatile變量的寫操作后,其后的任何讀操作理解可見此寫操作的結(jié)果。

  盡管volatile變量的特性不錯,但是volatile并不能保證線程安全的,也就是說volatile字段的操作不是原子性的,volatile變量只能保證可見性(一個線程修改后其它線程能夠理解看到此變化后的結(jié)果),要想保證原子性,目前為止只能加鎖!

使用Volatile的原則:

  應(yīng)用volatile變量的三個原則:

  (1)寫入變量不依賴此變量的值,或者只有一個線程修改此變量

  (2)變量的狀態(tài)不需要與其它變量共同參與不變約束

  (3)訪問變量不需要加鎖

  實際上,這些條件表明,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀態(tài),包括變量的當(dāng)前狀態(tài)。

  第一個條件的限制使 volatile 變量不能用作線程安全計數(shù)器。雖然增量操作(x++)看上去類似一個單獨操作,實際上它是一個由讀取-修改-寫入操作序列組成的組合操作,必須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性。實現(xiàn)正確的操作需要使 x 的值在操作期間保持不變,而 volatile 變量無法實現(xiàn)這點。(然而,如果將值調(diào)整為只從單個線程寫入,那么可以忽略第一個條件。)

  大多數(shù)編程情形都會與這三個條件的其中之一沖突,使得 volatile 變量不能像 synchronized 那樣普遍適用于實現(xiàn)線程安全。清單 1 顯示了一個非線程安全的數(shù)值范圍類。它包含了一個不變式 -- 下界總是小于或等于上界。

正確使用volatile:

  模式 #1:狀態(tài)標(biāo)志

  也許實現(xiàn) volatile 變量的規(guī)范使用僅僅是使用一個布爾狀態(tài)標(biāo)志,用于指示發(fā)生了一個重要的一次性事件,例如完成初始化或請求停機。

  很多應(yīng)用程序包含了一種控制結(jié)構(gòu),形式為 "在還沒有準備好停止程序時再執(zhí)行一些工作",如清單 2 所示:

  清單 2. 將 volatile 變量作為狀態(tài)標(biāo)志使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
volatile boolean shutdownRequested;
 
 …
 
 public void shutdown() { shutdownRequested = true; }
 
 public void doWork() {
 
 while (!shutdownRequested) {
 
 // do stuff
 
 }
 
 }

  很可能會從循環(huán)外部調(diào)用 shutdown() 方法 -- 即在另一個線程中 -- 因此,需要執(zhí)行某種同步來確保正確實現(xiàn) shutdownRequested變量的可見性。(可能會從 JMX 偵聽程序、GUI 事件線程中的操作偵聽程序、通過 RMI 、通過一個 Web 服務(wù)等調(diào)用)。然而,使用synchronized 塊編寫循環(huán)要比使用清單 2 所示的 volatile 狀態(tài)標(biāo)志編寫麻煩很多。由于 volatile 簡化了編碼,并且狀態(tài)標(biāo)志并不依賴于程序內(nèi)任何其他狀態(tài),因此此處非常適合使用 volatile.

  這種類型的狀態(tài)標(biāo)記的一個公共特性是:通常只有一種狀態(tài)轉(zhuǎn)換;shutdownRequested 標(biāo)志從 false 轉(zhuǎn)換為 true,然后程序停止。這種模式可以擴展到來回轉(zhuǎn)換的狀態(tài)標(biāo)志,但是只有在轉(zhuǎn)換周期不被察覺的情況下才能擴展(從 false 到 true,再轉(zhuǎn)換到 false)。此外,還需要某些原子狀態(tài)轉(zhuǎn)換機制,例如原子變量。

  模式 #2:一次性安全發(fā)布(one-time safe publication)

  缺乏同步會導(dǎo)致無法實現(xiàn)可見性,這使得確定何時寫入對象引用而不是原語值變得更加困難。在缺乏同步的情況下,可能會遇到某個對象引用的更新值(由另一個線程寫入)和該對象狀態(tài)的舊值同時存在。(這就是造成著名的雙重檢查鎖定(double-checked-locking)問題的根源,其中對象引用在沒有同步的情況下進行讀操作,產(chǎn)生的問題是您可能會看到一個更新的引用,但是仍然會通過該引用看到不完全構(gòu)造的對象)。

  實現(xiàn)安全發(fā)布對象的一種技術(shù)就是將對象引用定義為 volatile 類型。清單 3 展示了一個示例,其中后臺線程在啟動階段從數(shù)據(jù)庫加載一些數(shù)據(jù)。其他代碼在能夠利用這些數(shù)據(jù)時,在使用之前將檢查這些數(shù)據(jù)是否曾經(jīng)發(fā)布過。

  清單 3. 將 volatile 變量用于一次性安全發(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
public class BackgroundFloobleLoader {
 
  public volatile Flooble theFlooble;
 
  public void initInBackground() {
 
  // do lots of stuff
 
  theFlooble = new Flooble(); // this is the only write to theFlooble
 
  }
 
  }
 
  public class SomeOtherClass {
 
  public void doWork() {
 
  while (true) {
 
  // do some stuff…
 
  // use the Flooble, but only if it is ready
 
  if (floobleLoader.theFlooble != null)
 
  doSomething(floobleLoader.theFlooble);
 
  }
 
  }
 
  }

  如果 theFlooble 引用不是 volatile 類型,doWork() 中的代碼在解除對 theFlooble 的引用時,將會得到一個不完全構(gòu)造的 Flooble.

  該模式的一個必要條件是:被發(fā)布的對象必須是線程安全的,或者是有效的不可變對象(有效不可變意味著對象的狀態(tài)在發(fā)布之后永遠不會被修改)。volatile 類型的引用可以確保對象的發(fā)布形式的可見性,但是如果對象的狀態(tài)在發(fā)布后將發(fā)生更改,那么就需要額外的同步。

  模式 #3:獨立觀察(independent observation)

  安全使用 volatile 的另一種簡單模式是:定期 "發(fā)布" 觀察結(jié)果供程序內(nèi)部使用。例如,假設(shè)有一種環(huán)境傳感器能夠感覺環(huán)境溫度。一個后臺線程可能會每隔幾秒讀取一次該傳感器,并更新包含當(dāng)前文檔的 volatile 變量。然后,其他線程可以讀取這個變量,從而隨時能夠看到最新的溫度值。

  使用該模式的另一種應(yīng)用程序就是收集程序的統(tǒng)計信息。清單 4 展示了身份驗證機制如何記憶最近一次登錄的用戶的名字。將反復(fù)使用 lastUser 引用來發(fā)布值,以供程序的其他部分使用。

  清單 4. 將 volatile 變量用于多個獨立觀察結(jié)果的發(fā)布

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UserManager {
 
  public volatile String lastUser;
 
  public boolean authenticate(String user, String password) {
 
  boolean valid = passwordIsValid(user, password);
 
  if (valid) {
 
  User u = new User();
 
  activeUsers.add(u);
 
  lastUser = user;
 
  }
 
  return valid;
 
  }
 
  }

  該模式是前面模式的擴展;將某個值發(fā)布以在程序內(nèi)的其他地方使用,但是與一次性事件的發(fā)布不同,這是一系列獨立事件。這個模式要求被發(fā)布的值是有效不可變的 -- 即值的狀態(tài)在發(fā)布后不會更改。使用該值的代碼需要清楚該值可能隨時發(fā)生變化。

  模式 #4:"volatile bean" 模式

  volatile bean 模式適用于將 JavaBeans 作為"榮譽結(jié)構(gòu)"使用的框架。在 volatile bean 模式中,JavaBean 被用作一組具有 getter 和/或 setter 方法 的獨立屬性的容器。volatile bean 模式的基本原理是:很多框架為易變數(shù)據(jù)的持有者(例如 HttpSession)提供了容器,但是放入這些容器中的對象必須是線程安全的。

  在 volatile bean 模式中,JavaBean 的所有數(shù)據(jù)成員都是 volatile 類型的,并且 getter 和 setter 方法必須非常普通 -- 除了獲取或設(shè)置相應(yīng)的屬性外,不能包含任何邏輯。此外,對于對象引用的數(shù)據(jù)成員,引用的對象必須是有效不可變的。(這將禁止具有數(shù)組值的屬性,因為當(dāng)數(shù)組引用被聲明為 volatile 時,只有引用而不是數(shù)組本身具有 volatile 語義)。對于任何 volatile 變量,不變式或約束都不能包含 JavaBean 屬性。清單 5 中的示例展示了遵守 volatile bean 模式的 JavaBean:

  模式 #4:"volatile bean" 模式

?
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
@ThreadSafe
 
  public class Person {
 
  private volatile String firstName;
 
  private volatile String lastName;
 
  private volatile int age;
 
  public String getFirstName() { return firstName; }
 
  public String getLastName() { return lastName; }
 
  public int getAge() { return age; }
 
  public void setFirstName(String firstName) {
 
  this.firstName = firstName;
 
  }
 
  public void setLastName(String lastName) {
 
  this.lastName = lastName;
 
  }
 
  public void setAge(int age) {
 
  this.age = age;
 
  }
 
  }

  volatile 的高級模式

  前面幾節(jié)介紹的模式涵蓋了大部分的基本用例,在這些模式中使用 volatile 非常有用并且簡單。這一節(jié)將介紹一種更加高級的模式,在該模式中,volatile 將提供性能或可伸縮性優(yōu)勢。

  volatile 應(yīng)用的的高級模式非常脆弱。因此,必須對假設(shè)的條件仔細證明,并且這些模式被嚴格地封裝了起來,因為即使非常小的更改也會損壞您的代碼!同樣,使用更高級的 volatile 用例的原因是它能夠提升性能,確保在開始應(yīng)用高級模式之前,真正確定需要實現(xiàn)這種性能獲益。需要對這些模式進行權(quán)衡,放棄可讀性或可維護性來換取可能的性能收益 -- 如果您不需要提升性能(或者不能夠通過一個嚴格的測試程序證明您需要它),那么這很可能是一次糟糕的交易,因為您很可能會得不償失,換來的東西要比放棄的東西價值更低。

  模式 #5:開銷較低的讀-寫鎖策略

  目前為止,您應(yīng)該了解了 volatile 的功能還不足以實現(xiàn)計數(shù)器。因為 ++x 實際上是三種操作(讀、添加、存儲)的簡單組合,如果多個線程湊巧試圖同時對 volatile 計數(shù)器執(zhí)行增量操作,那么它的更新值有可能會丟失。

  然而,如果讀操作遠遠超過寫操作,您可以結(jié)合使用內(nèi)部鎖和 volatile 變量來減少公共代碼路徑的開銷。清單 6 中顯示的線程安全的計數(shù)器使用 synchronized 確保增量操作是原子的,并使用 volatile 保證當(dāng)前結(jié)果的可見性。如果更新不頻繁的話,該方法可實現(xiàn)更好的性能,因為讀路徑的開銷僅僅涉及 volatile 讀操作,這通常要優(yōu)于一個無競爭的鎖獲取的開銷。

  清單 6. 結(jié)合使用 volatile 和 synchronized 實現(xiàn) "開銷較低的讀-寫鎖"

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ThreadSafe
 
  public class CheesyCounter {
 
  // Employs the cheap read-write lock trick
 
  // All mutative operations MUST be done with the 'this' lock held
 
  @GuardedBy("this") private volatile int value;
 
  public int getValue() { return value; }
 
  public synchronized int increment() {
 
  return value++;
 
  }
 
  }

  之所以將這種技術(shù)稱之為 "開銷較低的讀-寫鎖" 是因為您使用了不同的同步機制進行讀寫操作。因為本例中的寫操作違反了使用 volatile 的第一個條件,因此不能使用 volatile 安全地實現(xiàn)計數(shù)器 -- 您必須使用鎖。然而,您可以在讀操作中使用 volatile 確保當(dāng)前值的可見性,因此可以使用鎖進行所有變化的操作,使用 volatile 進行只讀操作。其中,鎖一次只允許一個線程訪問值,volatile 允許多個線程執(zhí)行讀操作,因此當(dāng)使用 volatile 保證讀代碼路徑時,要比使用鎖執(zhí)行全部代碼路徑獲得更高的共享度 -- 就像讀-寫操作一樣。然而,要隨時牢記這種模式的弱點:如果超越了該模式的最基本應(yīng)用,結(jié)合這兩個競爭的同步機制將變得非常困難。

關(guān)于指令重排序與Happens-before法則

1、令重排序

  Java語言規(guī)范規(guī)定了JVM線程內(nèi)部維持順序化語義,也就是說只要程序的最終結(jié)果等同于它在嚴格的順序化環(huán)境下的結(jié)果,那么指令的執(zhí)行順序就可能與代碼的順序不一致。這個過程通過叫做指令的重排序。指令重排序存在的意義在于:JVM能夠根據(jù)處理器的特性(CPU的多級緩存系統(tǒng)、多核處理器等)適當(dāng)?shù)闹匦屡判驒C器指令,使機器指令更符合CPU的執(zhí)行特點,最大限度的發(fā)揮機器的性能。

  程序執(zhí)行最簡單的模型是按照指令出現(xiàn)的順序執(zhí)行,這樣就與執(zhí)行指令的CPU無關(guān),最大限度的保證了指令的可移植性。這個模型的專業(yè)術(shù)語叫做順序化一致性模型。但是現(xiàn)代計算機體系和處理器架構(gòu)都不保證這一點(因為人為的指定并不能總是保證符合CPU處理的特性)。

2、appens-before法則

  Java存儲模型有一個happens-before原則,就是如果動作B要看到動作A的執(zhí)行結(jié)果(無論A/B是否在同一個線程里面執(zhí)行),那么A/B就需要滿足happens-before關(guān)系。

  在介紹happens-before法則之前介紹一個概念:JMM動作(Java Memeory Model Action),Java存儲模型動作。一個動作(Action)包括:變量的讀寫、監(jiān)視器加鎖和釋放鎖、線程的start()和join()。后面還會提到鎖的的。

  happens-before完整規(guī)則:

  (1)同一個線程中的每個Action都happens-before于出現(xiàn)在其后的任何一個Action.

  (2)對一個監(jiān)視器的解鎖happens-before于每一個后續(xù)對同一個監(jiān)視器的加鎖。

  (3)對volatile字段的寫入操作happens-before于每一個后續(xù)的同一個字段的讀操作。

  (4)Thread.start()的調(diào)用會happens-before于啟動線程里面的動作。

  (5)Thread中的所有動作都happens-before于其他線程檢查到此線程結(jié)束或者Thread.join()中返回或者Thread.isAlive()==false.

  (6)一個線程A調(diào)用另一個另一個線程B的interrupt()都happens-before于線程A發(fā)現(xiàn)B被A中斷(B拋出異常或者A檢測到B的isInterrupted()或者interrupted())。

  (7)一個對象構(gòu)造函數(shù)的結(jié)束happens-before與該對象的finalizer的開始

  (8)如果A動作happens-before于B動作,而B動作happens-before與C動作,那么A動作happens-before于C動作。

以上就是本文的全部內(nèi)容,關(guān)于volatile變量就為大家介紹到這里,希望對大家學(xué)習(xí)了解java中的volatile變量有所幫助。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 手机在线免费观看高清 | 国产香蕉一区二区精品视频 | 亚洲香蕉网久久综合影院3p | 男人使劲躁女人视频免费 | 亚洲国内精品 | 亚洲热图 | 男模chinesegayxxxx | 色偷偷影院 | 亚洲va在线va天堂成人 | 嘿嘿午夜 | 欧美专区综合 | 无码人妻99久久密AV | 亚洲精品www久久久久久 | 精品久久亚洲 | 亚洲AV久久无码精品九号 | 国产成人在线小视频 | bt天堂在线观看国产 | 精品国产在线观看 | 国产男女乱淫真视频全程播放 | 国内小情侣一二三区在线视频 | 欧美黑人换爱交换乱理伦片 | 天堂久久久久va久久久久 | 日本不卡在线观看免费v | 美女的让男人桶爽免费看 | 忘忧草在线 | 日日视频| 按摩师他揉我奶好爽捏我奶 | 国模丰满美女冰漪34d | 男人操女人动图 | 羞羞漫画免费漫画页面在线看漫画秋蝉 | 国产农村乱子伦精品视频 | 国产精品亚洲片在线不卡 | 国产一区在线看 | 妹妹骑上来蹭着蹭着就射了 | 91大神在线观看精品一区 | 成人看片免费无限观看视频 | 波多野结衣中文丝袜字幕 | 亚洲成人第一 | 99热久久国产精品这里 | 暖暖暖免费观看在线观看 | 日本天堂视频在线观看 |