最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中Volatile關(guān)鍵字詳解及代碼示例

 更新時間:2017年11月15日 09:39:14   作者:鄭州的文武  
這篇文章主要介紹了Java中Volatile關(guān)鍵字詳解及代碼示例,分為兩個部分,第一部分介紹了Volatile關(guān)鍵字的基本概念等內(nèi)容,第二部分分享了實例代碼,具有一定參考價值,需要的朋友可以了解下。

一、基本概念

先補充一下概念:Java內(nèi)存模型中的可見性、原子性和有序性。

可見性:

可見性是一種復(fù)雜的屬性,因為可見性中的錯誤總是會違背我們的直覺。通常,我們無法確保執(zhí)行讀操作的線程能適時地看到其他線程寫入的值,有時甚至是根本不可能的事情。為了確保多個線程之間對內(nèi)存寫入操作的可見性,必須使用同步機制。

可見性,是指線程之間的可見性,一個線程修改的狀態(tài)對另一個線程是可見的。也就是一個線程修改的結(jié)果。另一個線程馬上就能看到。比如:用volatile修飾的變量,就會具有可見性。volatile修飾的變量不允許線程內(nèi)部緩存和重排序,即直接修改內(nèi)存。所以對其他線程是可見的。但是這里需要注意一個問題,volatile只能讓被他修飾內(nèi)容具有可見性,但不能保證它具有原子性。比如volatileinta=0;之后有一個操作a++;這個變量a具有可見性,但是a++依然是一個非原子操作,也就是這個操作同樣存在線程安全問題。

在Java中volatile、synchronized和final實現(xiàn)可見性。

原子性:

原子是世界上的最小單位,具有不可分割性。比如a=0;(a非long和double類型)這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++;這個操作實際是a=a+1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(shù)(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

在Java中synchronized和在lock、unlock中操作保證原子性。

有序性:

Java語言提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性,volatile是因為其本身包含“禁止指令重排序”的語義,synchronized是由“一個變量在同一個時刻只允許一條線程對其進行l(wèi)ock操作”這條規(guī)則獲得的,此規(guī)則決定了持有同一個對象鎖的兩個同步塊只能串行執(zhí)行。

下面內(nèi)容摘錄自《JavaConcurrencyinPractice》:

下面一段代碼在多線程環(huán)境下,將存在問題。

+ View code
/**
 * @author zhengbinMac
 */
public class NoVisibility {
 private static boolean ready;
 private static int number;
 private static class ReaderThread extends Thread {
  @Override
  public void run() {
   while(!ready) {
    Thread.yield();
   }
   System.out.println(number);
  }
 }
 public static void main(String[] args) {
  new ReaderThread().start();
  number = 42;
  ready = true;
 }
}

  NoVisibility可能會持續(xù)循環(huán)下去,因為讀線程可能永遠都看不到ready的值。甚至NoVisibility可能會輸出0,因為讀線程可能看到了寫入ready的值,但卻沒有看到之后寫入number的值,這種現(xiàn)象被稱為“重排序”。只要在某個線程中無法檢測到重排序情況(即使在其他線程中可以明顯地看到該線程中的重排序),那么就無法確保線程中的操作將按照程序中指定的順序來執(zhí)行。當主線程首先寫入number,然后在沒有同步的情況下寫入ready,那么讀線程看到的順序可能與寫入的順序完全相反。

  在沒有同步的情況下,編譯器、處理器以及運行時等都可能對操作的執(zhí)行順序進行一些意想不到的調(diào)整。在缺乏足夠同步的多線程程序中,要想對內(nèi)存操作的執(zhí)行春旭進行判斷,無法得到正確的結(jié)論。

  這個看上去像是一個失敗的設(shè)計,但卻能使JVM充分地利用現(xiàn)代多核處理器的強大性能。例如,在缺少同步的情況下,Java內(nèi)存模型允許編譯器對操作順序進行重排序,并將數(shù)值緩存在寄存器中。此外,它還允許CPU對操作順序進行重排序,并將數(shù)值緩存在處理器特定的緩存中。

二、Volatile原理

  Java語言提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操作通知到其他線程。當把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。

  在訪問volatile變量時不會執(zhí)行加鎖操作,因此也就不會使執(zhí)行線程阻塞,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級的同步機制。

  當對非 volatile 變量進行讀寫的時候,每個線程先從內(nèi)存拷貝變量到CPU緩存中。如果計算機有多個CPU,每個線程可能在不同的CPU上被處理,這意味著每個線程可以拷貝到不同的 CPU cache 中。

  而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache 這一步。

當一個變量定義為 volatile 之后,將具備兩種特性:

  1.保證此變量對所有的線程的可見性,這里的“可見性”,如本文開頭所述,當一個線程修改了這個變量的值,volatile 保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。但普通變量做不到這點,普通變量的值在線程間傳遞均需要通過主內(nèi)存(詳見:Java內(nèi)存模型)來完成。

  2.禁止指令重排序優(yōu)化。有volatile修飾的變量,賦值后多執(zhí)行了一個“l(fā)oad addl $0x0, (%esp)”操作,這個操作相當于一個內(nèi)存屏障(指令重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置),只有一個CPU訪問內(nèi)存時,并不需要內(nèi)存屏障;(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應(yīng)電路單元處理)。

volatile 性能:

  volatile 的讀性能消耗與普通變量幾乎相同,但是寫操作稍慢,因為它需要在本地代碼中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。

volatile關(guān)鍵字代碼示例

volatile關(guān)鍵字的兩層語義

一旦一個共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile修飾之后,那么就具備了兩層語義:

1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。

2)禁止進行指令重排序。

先看一段代碼,假如線程1先執(zhí)行,線程2后執(zhí)行:

//線程1
boolean stop = false;
while(!stop){
 doSomething();
}
 
//線程2
stop = true;

這段代碼是很典型的一段代碼,很多人在中斷線程時可能都會采用這種標記辦法。但是事實上,這段代碼會完全運行正確么?即一定會將線程中斷么?不一定,也許在大多數(shù)時候,這個代碼能夠把線程中斷,但是也有可能會導(dǎo)致無法中斷線程(雖然這個可能性很小,但是只要一旦發(fā)生這種情況就會造成死循環(huán)了)。

下面解釋一下這段代碼為何有可能導(dǎo)致無法中斷線程。在前面已經(jīng)解釋過,每個線程在運行過程中都有自己的工作內(nèi)存,那么線程1在運行的時候,會將stop變量的值拷貝一份放在自己的工作內(nèi)存當中。

那么當線程2更改了stop變量的值之后,但是還沒來得及寫入主存當中,線程2轉(zhuǎn)去做其他事情了,那么線程1由于不知道線程2對stop變量的更改,因此還會一直循環(huán)下去。

但是用volatile修飾之后就變得不一樣了:

第一:使用volatile關(guān)鍵字會強制將修改的值立即寫入主存;

第二:使用volatile關(guān)鍵字的話,當線程2進行修改時,會導(dǎo)致線程1的工作內(nèi)存中緩存變量stop的緩存行無效(反映到硬件層的話,就是CPU的L1或者L2緩存中對應(yīng)的緩存行無效);

第三:由于線程1的工作內(nèi)存中緩存變量stop的緩存行無效,所以線程1再次讀取變量stop的值時會去主存讀取。

那么在線程2修改stop值時(當然這里包括2個操作,修改線程2工作內(nèi)存中的值,然后將修改后的值寫入內(nèi)存),會使得線程1的工作內(nèi)存中緩存變量stop的緩存行無效,然后線程1讀取時,發(fā)現(xiàn)自己的緩存行無效,它會等待緩存行對應(yīng)的主存地址被更新之后,然后去對應(yīng)的主存讀取最新的值。

那么線程1讀取到的就是最新的正確的值。

2.volatile保證原子性嗎?

從上面知道volatile關(guān)鍵字保證了操作的可見性,但是volatile能保證對變量的操作是原子性嗎?

下面看一個例子:

public class Test {
 public volatile int inc = 0;
 
 public void increase() {
  inc++;
 }
 
 public static void main(String[] args) {
  final Test test = new Test();
  for(int i=0;i<10;i++){
   new Thread(){
    public void run() {
     for(int j=0;j<1000;j++)
      test.increase();
    };
   }.start();
  }
 
  while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
   Thread.yield();
  System.out.println(test.inc);
 }
}

大家想一下這段程序的輸出結(jié)果是多少?也許有些朋友認為是10000。但是事實上運行它會發(fā)現(xiàn)每次運行結(jié)果都不一致,都是一個小于10000的數(shù)字。

可能有的朋友就會有疑問,不對啊,上面是對變量inc進行自增操作,由于volatile保證了可見性,那么在每個線程中對inc自增完之后,在其他線程中都能看到修改后的值啊,所以有10個線程分別進行了1000次操作,那么最終inc的值應(yīng)該是1000*10=10000。

這里面就有一個誤區(qū)了,volatile關(guān)鍵字能保證可見性沒有錯,但是上面的程序錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變量的操作的原子性。

在前面已經(jīng)提到過,自增操作是不具備原子性的,它包括讀取變量的原始值、進行加1操作、寫入工作內(nèi)存。那么就是說自增操作的三個子操作可能會分割開執(zhí)行,就有可能導(dǎo)致下面這種情況出現(xiàn):

假如某個時刻變量inc的值為10,

線程1對變量進行自增操作,線程1先讀取了變量inc的原始值,然后線程1被阻塞了;

然后線程2對變量進行自增操作,線程2也去讀取變量inc的原始值,由于線程1只是對變量inc進行讀取操作,而沒有對變量進行修改操作,所以不會導(dǎo)致線程2的工作內(nèi)存中緩存變量inc的緩存行無效,所以線程2會直接去主存讀取inc的值,發(fā)現(xiàn)inc的值時10,然后進行加1操作,并把11寫入工作內(nèi)存,最后寫入主存。

然后線程1接著進行加1操作,由于已經(jīng)讀取了inc的值,注意此時在線程1的工作內(nèi)存中inc的值仍然為10,所以線程1對inc進行加1操作后inc的值為11,然后將11寫入工作內(nèi)存,最后寫入主存。

那么兩個線程分別進行了一次自增操作后,inc只增加了1。

解釋到這里,可能有朋友會有疑問,不對啊,前面不是保證一個變量在修改volatile變量時,會讓緩存行無效嗎?然后其他線程去讀就會讀到新的值,對,這個沒錯。這個就是上面的happens-before規(guī)則中的volatile變量規(guī)則,但是要注意,線程1對變量進行讀取操作之后,被阻塞了的話,并沒有對inc值進行修改。然后雖然volatile能保證線程2對變量inc的值讀取是從內(nèi)存中讀取的,但是線程1沒有進行修改,所以線程2根本就不會看到修改的值。

根源就在這里,自增操作不是原子性操作,而且volatile也無法保證對變量的任何操作都是原子性的。

把上面的代碼改成以下任何一種都可以達到效果:

采用synchronized:

public class Test {
 public int inc = 0;
 
 public synchronized void increase() {
  inc++;
 }
 
 public static void main(String[] args) {
  final Test test = new Test();
  for(int i=0;i<10;i++){
   new Thread(){
    public void run() {
     for(int j=0;j<1000;j++)
      test.increase();
    };
   }.start();
  }
 
  while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
   Thread.yield();
  System.out.println(test.inc);
 }
}

采用Lock:

public class Test {
 public int inc = 0;
 Lock lock = new ReentrantLock();
 
 public void increase() {
  lock.lock();
  try {
   inc++;
  } finally{
   lock.unlock();
  }
 }
 
 public static void main(String[] args) {
  final Test test = new Test();
  for(int i=0;i<10;i++){
   new Thread(){
    public void run() {
     for(int j=0;j<1000;j++)
      test.increase();
    };
   }.start();
  }
 
  while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
   Thread.yield();
  System.out.println(test.inc);
 }
}

采用AtomicInteger:

public class Test {
 public AtomicInteger inc = new AtomicInteger();
 
 public void increase() {
  inc.getAndIncrement();
 }
 
 public static void main(String[] args) {
  final Test test = new Test();
  for(int i=0;i<10;i++){
   new Thread(){
    public void run() {
     for(int j=0;j<1000;j++)
      test.increase();
    };
   }.start();
  }
 
  while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
   Thread.yield();
  System.out.println(test.inc);
 }
}

總結(jié)

以上就是本文關(guān)于Java中Volatile關(guān)鍵字詳解及代碼示例的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:

淺談Java編程中的synthetic關(guān)鍵字

如有不足之處,歡迎留言指出。

相關(guān)文章

  • Spring boot整合log4j2過程解析

    Spring boot整合log4j2過程解析

    這篇文章主要介紹了Spring boot整合log4j2過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • 2024年最新IntelliJ?IDEA常用的小技巧總結(jié)(JAVA新手上路必備)

    2024年最新IntelliJ?IDEA常用的小技巧總結(jié)(JAVA新手上路必備)

    這篇文章主要介紹了2024年最新IntelliJ?IDEA常用小技巧的相關(guān)資料,文中包括IntelliJ?IDEA的概述、下載與安裝、快速創(chuàng)建并運行Java工程、詳細設(shè)置、快速開發(fā)、多模塊的IDEA工程以及最新變化,需要的朋友可以參考下
    2025-01-01
  • Spring AOP如何整合redis(注解方式)實現(xiàn)緩存統(tǒng)一管理詳解

    Spring AOP如何整合redis(注解方式)實現(xiàn)緩存統(tǒng)一管理詳解

    這篇文章主要給大家介紹了關(guān)于Spring AOP如何整合redis(注解方式)實現(xiàn)緩存統(tǒng)一管理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧
    2018-08-08
  • Java的Hibernate框架中的雙向主鍵關(guān)聯(lián)與雙向外鍵關(guān)聯(lián)

    Java的Hibernate框架中的雙向主鍵關(guān)聯(lián)與雙向外鍵關(guān)聯(lián)

    Hibernate想要實現(xiàn)雙向的關(guān)聯(lián)就必須在映射文件的兩端同時配置<one-to-one>,另外還要在主映射的一端采用foreign外鍵關(guān)聯(lián)屬性,下面我們就一起來看一下Java的Hibernate框架中的雙向主鍵關(guān)聯(lián)與雙向外鍵關(guān)聯(lián)方法:
    2016-06-06
  • Java內(nèi)存模型(JMM)及happens-before原理

    Java內(nèi)存模型(JMM)及happens-before原理

    這篇文章主要介紹了java內(nèi)存模型(JMM)及happens-before原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • java中Hibernate的狀態(tài)總結(jié)

    java中Hibernate的狀態(tài)總結(jié)

    在本篇內(nèi)容里小編給大家整理的是一篇關(guān)于java中Hibernate的狀態(tài)總結(jié)內(nèi)容,有興趣的朋友們可以學習下。
    2021-01-01
  • MyBatis存儲過程、MyBatis分頁、MyBatis一對多增刪改查操作

    MyBatis存儲過程、MyBatis分頁、MyBatis一對多增刪改查操作

    本文通過一段代碼給大家介紹了MyBatis存儲過程、MyBatis分頁、MyBatis一對多增刪改查操作,非常不錯,具有參考借鑒價值,感興趣的朋友一起看看吧
    2016-11-11
  • MyBatis中的配置文件詳解

    MyBatis中的配置文件詳解

    在?MyBatis?中,配置文件分為?全局配置文件(核心配置文件)和映射配置文件,本文給大家介紹MyBatis中的配置文件相關(guān)知識,感興趣的朋友一起看看吧
    2023-10-10
  • JVM入門之內(nèi)存結(jié)構(gòu)(堆、方法區(qū))

    JVM入門之內(nèi)存結(jié)構(gòu)(堆、方法區(qū))

    JVM 基本上是每家招聘公司都會問到的問題,它們會這么無聊問這些不切實際的問題嗎?很顯然不是。由 JVM 引發(fā)的故障問題,無論在我們開發(fā)過程中還是生產(chǎn)環(huán)境下都是非常常見的
    2021-06-06
  • springsecurity 基本使用詳解

    springsecurity 基本使用詳解

    這篇文章主要介紹了springsecurity 基本使用,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11

最新評論

贵州省| 广昌县| 龙陵县| 高碑店市| 南和县| 息烽县| 定兴县| 绥芬河市| 阳春市| 平乡县| 原平市| 滦平县| 马边| 台北县| 安仁县| 方城县| 恩平市| 慈利县| 通城县| 阳朔县| 永州市| 高碑店市| 辉南县| 清水县| 当雄县| 启东市| 沭阳县| 安平县| 邵阳县| 镶黄旗| 天峨县| 襄垣县| 泰安市| 南木林县| 东兴市| 大同市| 会昌县| 宽甸| 新建县| 宜兰市| 武安市|