乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      JAVA多線程之volatile 與 synchronized 的比較

       沙門空海 2018-01-16

      一,volatile關(guān)鍵字的可見性

      要想理解volatile關(guān)鍵字,得先了解下JAVA的內(nèi)存模型,Java內(nèi)存模型的抽象示意圖如下:

      從圖中可以看出:

      ①每個(gè)線程都有一個(gè)自己的本地內(nèi)存空間--線程??臻g???線程執(zhí)行時(shí),先把變量從主內(nèi)存讀取到線程自己的本地內(nèi)存空間,然后再對(duì)該變量進(jìn)行操作

      ②對(duì)該變量操作完后,在某個(gè)時(shí)間再把變量刷新回主內(nèi)存

      關(guān)于JAVA內(nèi)存模型,更詳細(xì)的可參考: 深入理解Java內(nèi)存模型(一)——基礎(chǔ)

      因此,就存在內(nèi)存可見性問題,看一個(gè)示例程序:(摘自書上)

      復(fù)制代碼
       1 public class RunThread extends Thread {
       2 
       3     private boolean isRunning = true;
       4 
       5     public boolean isRunning() {
       6         return isRunning;
       7     }
       8 
       9     public void setRunning(boolean isRunning) {
      10         this.isRunning = isRunning;
      11     }
      12 
      13     @Override
      14     public void run() {
      15         System.out.println("進(jìn)入到run方法中了");
      16         while (isRunning == true) {
      17         }
      18         System.out.println("線程執(zhí)行完成了");
      19     }
      20 }
      21 
      22 public class Run {
      23     public static void main(String[] args) {
      24         try {
      25             RunThread thread = new RunThread();
      26             thread.start();
      27             Thread.sleep(1000);
      28             thread.setRunning(false);
      29         } catch (InterruptedException e) {
      30             e.printStackTrace();
      31         }
      32     }
      33 }
      復(fù)制代碼

      Run.java 第28行,main線程 將啟動(dòng)的線程RunThread中的共享變量設(shè)置為false,從而想讓RunThread.java 第14行中的while循環(huán)結(jié)束。

      如果,我們使用JVM -server參數(shù)執(zhí)行該程序時(shí),RunThread線程并不會(huì)終止!從而出現(xiàn)了死循環(huán)!!

      原因分析: 

      現(xiàn)在有兩個(gè)線程,一個(gè)是main線程,另一個(gè)是RunThread。它們都試圖修改 第三行的 isRunning變量。按照J(rèn)VM內(nèi)存模型,main線程將isRunning讀取到本地線程內(nèi)存空間,修改后,再刷新回主內(nèi)存。

      而在JVM 設(shè)置成 -server模式運(yùn)行程序時(shí),線程會(huì)一直在私有堆棧中讀取isRunning變量。因此,RunThread線程無法讀到main線程改變的isRunning變量

      從而出現(xiàn)了死循環(huán),導(dǎo)致RunThread無法終止。這種情形,在《Effective JAVA》中,將之稱為“活性失敗”

      解決方法,在第三行代碼處用 volatile 關(guān)鍵字修飾即可。這里,它強(qiáng)制線程從主內(nèi)存中取 volatile修飾的變量。

          volatile private boolean isRunning = true;

       

      擴(kuò)展一下,當(dāng)多個(gè)線程之間需要根據(jù)某個(gè)條件確定 哪個(gè)線程可以執(zhí)行時(shí),要確保這個(gè)條件在 線程 之間是可見的。因此,可以用volatile修飾。

      綜上,volatile關(guān)鍵字的作用是:使變量在多個(gè)線程間可見(可見性)

       

      二,volatile關(guān)鍵字的非原子性

      所謂原子性,就是某系列的操作步驟要么全部執(zhí)行,要么都不執(zhí)行。

      比如,變量的自增操作 i++,分三個(gè)步驟:

      ①從內(nèi)存中讀取出變量 i 的值

      ②將 i 的值加1

      ③將 加1 后的值寫回內(nèi)存

      這說明 i++ 并不是一個(gè)原子操作。因?yàn)?,它分成了三步,有可能?dāng)某個(gè)線程執(zhí)行到了第②時(shí)被中斷了,那么就意味著只執(zhí)行了其中的兩個(gè)步驟,沒有全部執(zhí)行。

      關(guān)于volatile的非原子性,看個(gè)示例:

      復(fù)制代碼
       1 public class MyThread extends Thread {
       2     public volatile static int count;
       3 
       4     private static void addCount() {
       5         for (int i = 0; i < 100; i++) {
       6             count++;
       7         }
       8         System.out.println("count=" + count);
       9     }
      10 
      11     @Override
      12     public void run() {
      13         addCount();
      14     }
      15 }
      16 
      17 public class Run {
      18     public static void main(String[] args) {
      19         MyThread[] mythreadArray = new MyThread[100];
      20         for (int i = 0; i < 100; i++) {
      21             mythreadArray[i] = new MyThread();
      22         }
      23 
      24         for (int i = 0; i < 100; i++) {
      25             mythreadArray[i].start();
      26         }
      27     }
      28 }
      復(fù)制代碼

      MyThread類第2行,count變量使用volatile修飾

      Run.java 第20行 for循環(huán)中創(chuàng)建了100個(gè)線程,第25行將這100個(gè)線程啟動(dòng)去執(zhí)行 addCount(),每個(gè)線程執(zhí)行100次加1

      期望的正確的結(jié)果應(yīng)該是 100*100=10000,但是,實(shí)際上count并沒有達(dá)到10000

      原因是:volatile修飾的變量并不保證對(duì)它的操作(自增)具有原子性。(對(duì)于自增操作,可以使用JAVA的原子類AutoicInteger類保證原子自增)

      比如,假設(shè) i 自增到 5,線程A從主內(nèi)存中讀取i,值為5,將它存儲(chǔ)到自己的線程空間中,執(zhí)行加1操作,值為6。此時(shí),CPU切換到線程B執(zhí)行,從主從內(nèi)存中讀取變量i的值。由于線程A還沒有來得及將加1后的結(jié)果寫回到主內(nèi)存,線程B就已經(jīng)從主內(nèi)存中讀取了i,因此,線程B讀到的變量 i 值還是5

      相當(dāng)于線程B讀取的是已經(jīng)過時(shí)的數(shù)據(jù)了,從而導(dǎo)致線程不安全性。這種情形在《Effective JAVA》中稱之為“安全性失敗”

      綜上,僅靠volatile不能保證線程的安全性。(原子性)

       

      此外,volatile關(guān)鍵字修飾的變量不會(huì)被指令重排序優(yōu)化。這里以《深入理解JAVA虛擬機(jī)》中一個(gè)例子來說明下自己的理解:

      線程A執(zhí)行的操作如下:

      復(fù)制代碼
      Map configOptions ;
      char[] configText;
      
      volatile boolean initialized = false;
      
      //線程A首先從文件中讀取配置信息,調(diào)用process...處理配置信息,處理完成了將initialized 設(shè)置為true
      configOptions = new HashMap();
      configText = readConfigFile(fileName);
      processConfig(configText, configOptions);//負(fù)責(zé)將配置信息configOptions 成功初始化
      initialized = true;
      復(fù)制代碼

       

      線程B等待線程A把配置信息初始化成功后,使用配置信息去干活.....線程B執(zhí)行的操作如下:

      復(fù)制代碼
      while(!initialized)
      {
          sleep();
      }
      
      //使用配置信息干活
      doSomethingWithConfig();
      復(fù)制代碼

       

      如果initialized變量不用 volatile 修飾,在線程A執(zhí)行的代碼中就有可能指令重排序。

      即:線程A執(zhí)行的代碼中的最后一行:initialized = true 重排序到了 processConfig方法調(diào)用的前面執(zhí)行了,這就意味著:配置信息還未成功初始化,但是initialized變量已經(jīng)被設(shè)置成true了。那么就導(dǎo)致 線程B的while循環(huán)“提前”跳出,拿著一個(gè)還未成功初始化的配置信息去干活(doSomethingWithConfig方法)。。。。

      因此,initialized 變量就必須得用 volatile修飾。這樣,就不會(huì)發(fā)生指令重排序,也即:只有當(dāng)配置信息被線程A成功初始化之后,initialized 變量才會(huì)初始化為true。綜上,volatile 修飾的變量會(huì)禁止指令重排序(有序性)

       

      三,volatile 與 synchronized 的比較

      volatile主要用在多個(gè)線程感知實(shí)例變量被更改了場合,從而使得各個(gè)線程獲得最新的值。它強(qiáng)制線程每次從主內(nèi)存中講到變量,而不是從線程的私有內(nèi)存中讀取變量,從而保證了數(shù)據(jù)的可見性。

      關(guān)于synchronized,可參考:JAVA多線程之Synchronized關(guān)鍵字--對(duì)象鎖的特點(diǎn)

      比較:

      ①volatile輕量級(jí),只能修飾變量。synchronized重量級(jí),還可修飾方法

      ②volatile只能保證數(shù)據(jù)的可見性,不能用來同步,因?yàn)槎鄠€(gè)線程并發(fā)訪問volatile修飾的變量不會(huì)阻塞。

      synchronized不僅保證可見性,而且還保證原子性,因?yàn)?,只有獲得了鎖的線程才能進(jìn)入臨界區(qū),從而保證臨界區(qū)中的所有語句都全部執(zhí)行。多個(gè)線程爭搶synchronized鎖對(duì)象時(shí),會(huì)出現(xiàn)阻塞。

       

      四,線程安全性

      線程安全性包括兩個(gè)方面,①可見性。②原子性。

      從上面自增的例子中可以看出:僅僅使用volatile并不能保證線程安全性。而synchronized則可實(shí)現(xiàn)線程的安全性。

       

      參考:JAVA多線程之線程間的通信方式

      JAVA多線程之當(dāng)一個(gè)線程在執(zhí)行死循環(huán)時(shí)會(huì)影響另外一個(gè)線程嗎?

      JAVA多線程之wait/notify

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多