欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java并發(fā)編程基礎(chǔ)之volatile

 更新時間:2021年06月11日 08:47:43   作者:CoderSheeper  
volatile作為Java多線程中輕量級的同步措施,保證了多線程環(huán)境中“共享變量”的可見性。這里的可見性簡單而言可以理解為當(dāng)一個線程修改了一個共享變量的時候,另外的線程能夠讀到這個修改的值。本文將詳解介紹Java并發(fā)編程基礎(chǔ)之volatile

一、volatile的定義和實現(xiàn)原理

1、Java并發(fā)模型采用的方式

a)線程通信的機(jī)制主要有兩種:共享內(nèi)存和消息傳遞。

①共享內(nèi)存:線程之間共享程序的公共狀態(tài),通過寫-讀共享內(nèi)存中的公共狀態(tài)來進(jìn)行隱式通信;

②消息傳遞:線程之間沒有公共狀態(tài),線程之間 必須通過發(fā)送消息來顯式通信。

b)同步:用于控制不同線程之間操作發(fā)生相對順序。在

共享內(nèi)存模型中,同步是顯式的進(jìn)行的,需要顯示的指定某個方法或者代碼塊在線程執(zhí)行期間互斥進(jìn)行。

消息傳遞模型中,由于消息的發(fā)送必定在消息的接受之前,所以同步是隱式的進(jìn)行的。

c)Java并發(fā)采用的是共享內(nèi)存模型,線程之間通信總是隱式的進(jìn)行,而且這個通信是對程序員透明的。那么我們需要了解的是這個隱式通信的底層工作機(jī)制。

2、volatile的定義

Java編程語言中允許線程訪問共享變量,為了確保共享變量能夠被準(zhǔn)確和一致性的更新,線程應(yīng)該確保通過排它鎖單獨獲得這個變量。

3、volatile的底層實現(xiàn)原理

a)在編寫多線程程序中,使用volatile修飾的共享變量在進(jìn)行寫操作的時候,編譯器生成的匯編代碼中會多出一條lock指令,這條lock指令的作用:

  • ①將當(dāng)前處理器緩存行中的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存
  • ②這個寫回內(nèi)存的操作會使得其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效

b)參考下面的這張圖理解

二、volatile的內(nèi)存語義

1、volatile的特性

a)首先我們來看對單個變量的讀/寫的實現(xiàn)(單個變量的情況可以看做是對同一個鎖對這個變量的讀/寫進(jìn)行了同步),看下面的例子

package cn.jvm.test;

public class TestVolatile1 {

    volatile long var1 = 0L;

    public void set(long l) {
        // TODO Auto-generated method stub
        var1 = l;
    }

    public void getAndIncrement() {
        // TODO Auto-generated method stub
        var1 ++; //注意++操作
    }

    public long get() {
        return var1;
    }
}

上面的set和get操作在語義上和使用synchronized修飾后一樣,即下面的這種寫法

package cn.jvm.test;

public class TestVolatile1 {

    volatile long var1 = 0L;

    public synchronized void set(long l) {
        // TODO Auto-generated method stub
        var1 = l;
    }

    public synchronized long get() {
        return var1;
    }
}

b)但是在上面的用例中,我們使用的var1++操作,整體上沒有原子性,所以如果使用多線程方粉getAndIncrement方法的話,會導(dǎo)致讀出的數(shù)據(jù)和主存中不一致的情況。

c)volatile變量的特性

①可見性:對一個volatile變量的讀操作,總是能夠看到對這個volatile變量最后的寫入

②原子性:對任意單個volatile變量的讀寫具有原子性,但是對于volatile變量的復(fù)合型操作并不具備原子性

2、volatile寫-讀建立的happens-before關(guān)系

a)看下面的代碼實例

package cn.jvm.test;

public class TestVolatile2 {

    int a = 0;
    volatile boolean flag = false;

    public void writer() {
        a = 1;
        flag = true;
    }

    public void reader() {
        if(flag) {
            int i =a;
            //...其他操作
        }
    }
}

b)在上面的程序中,假設(shè)線程A執(zhí)行write方法,線程B執(zhí)行reader方法,根據(jù)happens-before規(guī)則有下面的關(guān)系:

程序次序規(guī)則:①happens-before②; ③happens-before④

volatile規(guī)則:②happens-before③

傳遞性規(guī)則:①happens-before④

所以可以得到下面的這個狀態(tài)圖

3、volatile的寫/讀內(nèi)存語義

a)下面是volatile的寫/讀內(nèi)存語義

①當(dāng)寫一個volatile變量時候,JMM會將線程對應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存中

②當(dāng)讀一個volatile變量的時候,JMM會將線程對應(yīng)的本地內(nèi)存置為無效,然后從主內(nèi)存中讀取共享變量

b)還是參照上面的程序示例,參考視圖的模型來進(jìn)行說明

①寫內(nèi)存語義的示意圖:假設(shè)線程A執(zhí)行writer方法,線程B執(zhí)行reader方法,初始狀況下線程A和B中的變量都是初始狀態(tài)

②寫內(nèi)存語義的示意圖:

三、volatile內(nèi)存語義的實現(xiàn)

我們上面說到的基本上從宏觀上而言都是說明了volatile保證內(nèi)存可見性問題,volatile的另一個語義就是禁止指令重排序的優(yōu)化。下面說一下volatile禁止指令重排序的實現(xiàn)細(xì)節(jié)

1、volatile重排序規(guī)則

①當(dāng)?shù)诙€操作是volatile寫的時候,不管第一個操作是什么,都不能進(jìn)行指令重排序。這個規(guī)則確保volatile寫之前的操作都不會被重排序到volatile寫之后。也是為了保證volatile寫對其他線程可見

②當(dāng)?shù)谝粋€操作為volatile讀的時候,不管第二個操作是什么,都不能進(jìn)行重排序。確保volatile讀之后的操作不會被重排序到volatile讀之前

③當(dāng)?shù)谝粋€操作是volatile寫,第二個操作是volatile讀的時候,不能進(jìn)行重排序

如下所示,上面的是下表中的總結(jié)。

2、內(nèi)存屏障  

編譯器在生成字節(jié)碼的時候,會在指令序列中插入內(nèi)存屏障來禁止對特定類型的處理器重排序。下面是集中策略,后面會說明這幾種情況

①在每個volatile寫操作之前插入StoreStore屏障

②在每個volatile寫操作之后插入StoreLoad屏障

③在每個volatile讀操作之后插入LoadLoad屏障

④在每個volatile讀操作之后插入LoadStore屏障

3、內(nèi)存屏障示例

a)volatile寫插入內(nèi)存屏障之后的指令序列圖

b)volatile讀插入內(nèi)存屏障后的指令序列圖

四、volatile與死循環(huán)問題

1、先看下面的示例代碼,觀察運(yùn)行結(jié)果,當(dāng)共享變量isRunning沒有被聲明為volatile的時候,main線程會在2秒之后將共享變量isRunning置為false并且輸出修改信息,這樣新建的線程應(yīng)該結(jié)束運(yùn)行,但是實際上并沒有,控制臺中會一直保持運(yùn)行的狀態(tài),并且不會打印線程結(jié)束執(zhí)行;如下所示

package cn.jvm.test;

class ThreadDemo extends Thread {
    private  boolean isRunning = true;
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 開始執(zhí)行");
        while(isRunning) {

        }
        System.out.println(Thread.currentThread().getName() + " 結(jié)束執(zhí)行");
    }
    public boolean isRunning() {
        return isRunning;
    }
    public void SetIsRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
}

public class TestVolatile4 {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        td.start();
        try {
            Thread.sleep(2000);
            td.SetIsRunning(false);
            System.out.println(Thread.currentThread().getName() + " 線程將共享變量值修改為false");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

2、分析出現(xiàn)上面結(jié)果的原因

在啟動線程ThreadDemo之后,變量isRunning被存在公共堆棧以及線程的私有堆棧中,后//續(xù)中線程一直在私有堆棧中取出isRunning的值,雖然main線程執(zhí)行SetIsRunning方法修改了isRunning的值,但是這個值并沒有被Thread-//0線程所知,就像上面說的Thread-0取得值一直都是私有堆棧中的,所以不會知道isRunning被修改,也就不會退出循環(huán)

3、按照上面的原因分析一下執(zhí)行的時候的工作內(nèi)存和主內(nèi)存的情況,按照下面的分析我們很容易得出結(jié)論

上面的問題就是因為工作內(nèi)存(私有堆棧)和主內(nèi)存(公共堆棧)中的值不同步。而按照我們上面說到的volatile使得單個變量保證線程可見性,就可以對程序修改保證共享變量在main線程中的修改對Thread-0線程可見(結(jié)合volatile的實現(xiàn)原理)

4、修改之后的結(jié)果

package cn.jvm.test;

class ThreadDemo extends Thread {
    private volatile boolean isRunning = true;
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 開始執(zhí)行");
        while(isRunning) {
            
        }
        System.out.println(Thread.currentThread().getName() + " 結(jié)束執(zhí)行");
    }
    public boolean isRunning() {
        return isRunning;
    }
    public void SetIsRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
}

public class TestVolatile4 {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        td.start();
        try {
            Thread.sleep(2000);
            td.SetIsRunning(false);
            System.out.println(Thread.currentThread().getName() + " 線程將共享變量值修改為false");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

五、volatile對于復(fù)合操作非原子性問題

1、volatile能保證對單個變量在多線程之間的可見性問題,但是對于單個變量的復(fù)合操作不能保證原子性,如下代碼示例,運(yùn)行結(jié)果為

當(dāng)然這個結(jié)果是隨機(jī)的,但是不能保證運(yùn)行結(jié)果是100000

在沒有使用同步操作之前,雖然count變量是volatile的,但是由于count++操作是個復(fù)合操作

①從內(nèi)存中取出count的值

②計算count的值

③將count的值寫到內(nèi)存中

這個復(fù)合操作由于volatile不能保證原子性,所以就會出現(xiàn)錯誤

package cn.jvm.test;

import java.util.ArrayList;
import java.util.List;

public class TestVolatile5 {
    volatile int count = 0;
    /*synchronized*/ void m(){
        for(int i = 0; i < 10000; i++){
            count++;
        }
    }

    public static void main(String[] args) {
        final TestVolatile5 t = new TestVolatile5();
        List<Thread> threads = new ArrayList<>();
        for(int i = 0; i < 10; i++){
            threads.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    t.m();
                }
            }));
        }
        for(Thread thread : threads){
            thread.start();
        }
        for(Thread thread : threads){
            try {
                thread.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(t.count);
    }
}

2、下面按照J(rèn)VM的內(nèi)存工作來分析一下,即當(dāng)前一個線程在計算count變量的時候,另一個線程已經(jīng)修改了count變量的值,這樣就必然會出現(xiàn)錯誤。所以對于這種復(fù)合操作就需要使用原子類或者使用synchronized來保證原子性(保證同步)

3、修改后的synchronized和使用原子類如下所示

package cn.jvm.test;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class TestVolatile5 {
     int count = 0;
     synchronized void m(){
         for(int i = 0; i < 10000; i++){
             count++;
         }
     }
 
     public static void main(String[] args) {
         final TestVolatile5 t = new TestVolatile5();
         List<Thread> threads = new ArrayList<>();
         for(int i = 0; i < 10; i++){
             threads.add(new Thread(new Runnable() {
                 @Override
                 public void run() {
                     t.m();
                 }
             }));
         }
         for(Thread thread : threads){
             thread.start();
         }
         for(Thread thread : threads){
             try {
                 thread.join();
             } catch (InterruptedException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
         System.out.println(t.count);
     }
 }
package cn.jvm.test;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class TestVolatile5 {
     AtomicInteger count = new AtomicInteger(0);
     void m(){
         for(int i = 0; i < 10000; i++){
             count.getAndIncrement();
         }
     }
 
     public static void main(String[] args) {
         final TestVolatile5 t = new TestVolatile5();
         List<Thread> threads = new ArrayList<>();
         for(int i = 0; i < 10; i++){
             threads.add(new Thread(new Runnable() {
                 @Override
                 public void run() {
                     t.m();
                 }
             }));
         }
         for(Thread thread : threads){
             thread.start();
         }
         for(Thread thread : threads){
             try {
                 thread.join();
             } catch (InterruptedException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
         System.out.println(t.count);
     }
 }

以上就是詳解Java并發(fā)編程基礎(chǔ)之volatile的詳細(xì)內(nèi)容,更多關(guān)于Java 并發(fā)編程 volatile的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • MyBatis-Plus枚舉和自定義主鍵ID的實現(xiàn)步驟

    MyBatis-Plus枚舉和自定義主鍵ID的實現(xiàn)步驟

    這篇文章主要給大家介紹了關(guān)于MyBatis-Plus枚舉和自定義主鍵ID的相關(guān)資料,文中通過實例代碼以及圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2022-02-02
  • swing jtextArea滾動條和文字縮放效果

    swing jtextArea滾動條和文字縮放效果

    這篇文章主要為大家詳細(xì)介紹了swing jtextArea滾動條和文字縮放效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • 手把手帶你了解Java-Stream流方法學(xué)習(xí)及總結(jié)

    手把手帶你了解Java-Stream流方法學(xué)習(xí)及總結(jié)

    這篇文章主要介紹了通過實例了解JavaStream流的方法學(xué)習(xí)和總結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2021-08-08
  • 關(guān)于socket發(fā)送數(shù)據(jù)需要注意的問題

    關(guān)于socket發(fā)送數(shù)據(jù)需要注意的問題

    這篇文章主要介紹了關(guān)于socket發(fā)送數(shù)據(jù)需要注意的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Mybatis返回結(jié)果封裝map過程解析

    Mybatis返回結(jié)果封裝map過程解析

    這篇文章主要介紹了Mybatis返回結(jié)果封裝map過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • SpringBoot之HandlerInterceptor攔截器的使用詳解

    SpringBoot之HandlerInterceptor攔截器的使用詳解

    這篇文章主要介紹了SpringBoot之HandlerInterceptor攔截器的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • java內(nèi)部類的定義與分類示例詳解

    java內(nèi)部類的定義與分類示例詳解

    這篇文章主要給大家介紹了關(guān)于java內(nèi)部類的定義與分類的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Java泛型類型通配符和C#對比分析

    Java泛型類型通配符和C#對比分析

    下面小編就為大家?guī)硪黄狫ava泛型類型通配符和C#對比分析。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-10-10
  • Springboot整合Socket實現(xiàn)單點發(fā)送,廣播群發(fā),1對1,1對多實戰(zhàn)

    Springboot整合Socket實現(xiàn)單點發(fā)送,廣播群發(fā),1對1,1對多實戰(zhàn)

    本文主要介紹了Springboot整合Socket實現(xiàn)單點發(fā)送,廣播群發(fā),1對1,1對多實戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • MyBatis源碼剖析之Mapper代理方式詳解

    MyBatis源碼剖析之Mapper代理方式詳解

    這篇文章主要為大家詳細(xì)介紹了MyBatis中Mapper代理的方式,文中將通過源碼為大家進(jìn)行詳細(xì)的剖析,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-07-07

最新評論