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

java并發(fā)編程之原子性、可見性、有序性

 更新時間:2021年09月28日 09:00:48   作者:早起的年輕人  
這篇文章主要給大家分享的是java并發(fā)編程的原子性、可見性和有序性,文章會具體舉例說明,感興趣的小伙伴可以參考一下文章的具體內(nèi)容

在java中,執(zhí)行下面這個語句

int i =12;

執(zhí)行線程必須先在自己的工作線程中對變量i所在的緩存行進行賦值操作,然后再寫入主存當中。而不是直接將數(shù)值10寫入主存(物理內(nèi)存)當中。

1 原子性

定義:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
舉個最簡單的例子,大家想一下假如為一個32位的變量賦值過程不具備原子性的話,會發(fā)生什么后果?

int i =12;

假若一個線程執(zhí)行到這個語句時,暫且假設為一個32位的變量賦值包括兩個過程:為低16位賦值,為高16位賦值。
那么就可能發(fā)生一種情況:當將低16位數(shù)值寫入之后,突然被中斷,而此時又有一個線程去讀取i的值,那么讀取到的就是錯誤的數(shù)據(jù)。

1.1 java中的原子性操作

在Java中,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要么執(zhí)行,要么不執(zhí)行。

例如:

int x = 10;     //語句1
int y = x;     //語句2
x++;           //語句3
x = x + 1;     //語句4

語句1是直接將數(shù)值10賦值給x,也就是說線程執(zhí)行這個語句的會直接將數(shù)值10寫入到工作內(nèi)存中,所以是原子性操作。

語句2實際上包含2個操作,它先要去讀取x的值,再將y的值寫入主存,雖然讀取x的值以及 將y的值寫入主存 這2個操作都是原子性操作,但是合起來就不是原子性操作了。
語句3 語句4 同理,先將x的值讀取到高速緩存中,然后+1賦值后,再寫入到主存中。

也就是說,只有簡單的讀取、賦值(而且必須是將數(shù)字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。

2 可見性

定義:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

2.1 可見性問題

例如:

//線程1
int i =12;
i=13;

//線程2 
int j=i;

假若執(zhí)行線程1的是CPU1,執(zhí)行線程2的是CPU2。當線程1執(zhí)行 i =13這句時,會先把i的初始值加載到CPU1的高速緩存中,然后賦值為13,那么在CPU1的高速緩存當中i的值變?yōu)?3了,卻沒有立即寫入到主存當中。

此時線程2執(zhí)行 j = i,它會先去主存讀取i的值并加載到CPU2的緩存當中,注意此時內(nèi)存當中i的值還是12,那么就會使得j的值為12,而不是13。

這就是可見性問題,也就是說 i 的值在線程一中修改了,沒有通知其他線程更新而導致的數(shù)據(jù)錯亂。

2.2 解決可見性問題

Java提供了volatile關鍵字來保證可見性。

也就是說當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內(nèi)存中讀取新值。

3 有序性

定義:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

3.1 單個線程內(nèi)程序的指令重排序

例如:

int i = 0;              
boolean flag = false;
i = 1;                //語句1  
flag = true;          //語句2

按照我們?nèi)粘5乃季S,程序的執(zhí)行過程是從上至下一行一行執(zhí)行的,就是說按照代碼的順序來執(zhí)行,那么JVM在實際中一定會這樣嗎??? 答案是否定的,這里可能會發(fā)生指令重排序(Instruction Reorder)。

指令重排序(Instruction Reorder 是指: 處理器為了提高程序運行效率,可能會對輸入代碼進行優(yōu)化,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致,但是它會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。

Java內(nèi)存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性。

比如上面的代碼中,語句1和語句2誰先執(zhí)行對最終的程序結(jié)果并沒有影響,那么就有可能在執(zhí)行過程中,語句2先執(zhí)行而語句1后執(zhí)行。

需要注意的是:處理器在進行重排序時是會考慮指令之間的數(shù)據(jù)依賴性,如果一個指令Instruction 2必須用到Instruction 1的結(jié)果,那么處理器會保證Instruction 1會在Instruction 2之前執(zhí)行。

3.2 多線程內(nèi)程序的指令重排序

重排序不會影響單個線程內(nèi)程序執(zhí)行的結(jié)果,但是多線程就不一定了。

//線程1:
context = loadContext();   //語句1
inited = true;             //語句2
 
//線程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

上面代碼中,由于語句1和語句2沒有數(shù)據(jù)依賴性,因此可能會被重排序。假如發(fā)生了重排序,在線程1執(zhí)行過程中先執(zhí)行語句2,而此是線程2會以為初始化工作已經(jīng)完成,那么就會跳出while循環(huán),去執(zhí)行doSomethingwithconfig(context)方法,而此時context并沒有被初始化,就會導致程序出錯。

3.3 保證有序性的解決方法

在Java里面,可以通過volatile關鍵字來保證一定的“有序性”。
當然可以通過synchronizedLock來保證有序性,很顯然,synchronizedLock保證每個時刻是有一個線程執(zhí)行同步代碼,相當于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。

3.4 volatile 保證有序性的原理

volatile關鍵字能禁止指令重排序,所以volatile能在一定程度上保證有序性,也就是說:

當程序執(zhí)行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經(jīng)進行,且結(jié)果已經(jīng)對后面的操作可見;在其后面的操作肯定還沒有進行;

在進行指令優(yōu)化時,不能將在對volatile變量訪問的語句放在其后面執(zhí)行,也不能把volatile變量后面的語句放到其前面執(zhí)行。

4 實例分析

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);
    }
}

一般說來 有10個線程分別進行了1000次操作,那么最終inc的值應該是1000*10=10000。但實際中并不是這樣,進行過測試后會發(fā)現(xiàn),每次執(zhí)行結(jié)束后,得到的都是一個比10000要小的值。

4.1 原理分析

自增操作是不具備原子性的,它包括讀取變量的原始值到高速緩存中、進行加1操作、寫入主存中這三個過程。
也就是說自增操作的三個子操作可能會分割開執(zhí)行,就有可能導致下面這種情況出現(xiàn):

假如某個時刻變量inc的值為10,
線程1對變量進行自增操作,線程1先讀取了變量inc的原始值,然后線程1被阻塞了;
然后線程2對變量進行自增操作,線程2也去讀取變量inc的原始值,
此時 變量inc的值還沒有任何改變,此時線程2拿到的值也為10,然后進行加1操作,然后將值11寫入到主存中,
然后線程1繼續(xù)進行加1操作 這里線程1中 inc的值依然為10,進行加1操作,然后將值11寫入到主存中

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

4.2 synchronized 結(jié)合

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);
    }
}

4.3 Lock 結(jié)合

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);
    }
}

4.4 使用AtomicInteger替換int

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);
    }
}

到此這篇關于java并發(fā)編程之原子性、可見性、有序性 的文章就介紹到這了,更多相關java并發(fā)編程之原子性、可見性、有序性 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring AI 入門學習指南

    Spring AI 入門學習指南

    本文介紹了如何使用SpringAI和Ollama進行AI開發(fā),包括如何配置和使用聊天、圖像、語音轉(zhuǎn)文字和文字轉(zhuǎn)語音模型,以及如何在Java項目中集成Ollama模型,感興趣的朋友一起看看吧
    2024-11-11
  • 掃二維碼自動跳轉(zhuǎn)【java】詳解

    掃二維碼自動跳轉(zhuǎn)【java】詳解

    這篇文章主要介紹了java掃二維碼自動跳轉(zhuǎn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-05-05
  • 整理總結(jié)Java多線程程序編寫的要點

    整理總結(jié)Java多線程程序編寫的要點

    這篇文章主要介紹了Java多線程程序編寫的要點,包括線程的狀態(tài)控制和優(yōu)先級以及線程的通信問題等方面,非常之全面!需要的朋友可以參考下
    2016-01-01
  • 深入了解Spring的事務傳播機制

    深入了解Spring的事務傳播機制

    Spring事務傳播機制是指,包含多個事務的方法在相互調(diào)用時,事務是如何在這些方法間傳播的。本文通過示例詳細介紹了Spring的事務傳播機制,需要的可以參考一下
    2022-09-09
  • Java阻塞隊列BlockingQueue基礎與使用

    Java阻塞隊列BlockingQueue基礎與使用

    本文詳細介紹了BlockingQueue家庭中的所有成員,包括他們各自的功能以及常見使用場景,通過實例代碼介紹了Java 阻塞隊列BlockingQueue的相關知識,需要的朋友可以參考下
    2023-01-01
  • 解決springcloud-gateway限流遇到的問題

    解決springcloud-gateway限流遇到的問題

    這篇文章主要介紹了解決springcloud-gateway限流遇到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring Boot 配置文件詳解(小結(jié))

    Spring Boot 配置文件詳解(小結(jié))

    Spring Boot提供了兩種常用的配置文件,分別是properties文件和yml文件。本章重點介紹yml的語法和從配置文件中取值。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • 輕松掌握Java適配器模式

    輕松掌握Java適配器模式

    這篇文章主要幫助大家輕松掌握Java適配器模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • SpringBoot中的@Inherited注解詳解

    SpringBoot中的@Inherited注解詳解

    這篇文章主要介紹了SpringBoot中的@Inherited注解詳解,@Inherited是一個標識,用來修飾注解,如果一個類用上了@Inherited修飾的注解,那么其子類也會繼承這個注解,需要的朋友可以參考下
    2023-08-08
  • SpringBoot整合activemq的案例代碼

    SpringBoot整合activemq的案例代碼

    ActiveMQ是消息隊列技術,為解決高并發(fā)問題而生,本文通過案例代碼給大家介紹pringBoot整合activemq的詳細過程,感興趣的朋友跟隨小編一起看看吧
    2022-02-02

最新評論

西贡区| 鹿邑县| 临清市| 泸州市| 苍溪县| 巴楚县| 济源市| 塔城市| 靖宇县| 利辛县| 水城县| 昌宁县| 平凉市| 高平市| 金秀| 新建县| 福安市| 正镶白旗| 青海省| 将乐县| 金寨县| 永济市| 江口县| 资溪县| 蒙阴县| 庆城县| 孟津县| 苏尼特左旗| 柘荣县| 泊头市| 德江县| 达拉特旗| 海口市| 潢川县| 盱眙县| 玉田县| 阳江市| 六枝特区| 仪征市| 射洪县| 芷江|