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

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - java并發編程之原子性、可見性、有序性

java并發編程之原子性、可見性、有序性

2022-01-21 00:45早起的年輕人 Java教程

這篇文章主要給大家分享的是java并發編程的原子性、可見性和有序性,文章會具體舉例說明,感興趣的小伙伴可以參考一下文章的具體內容

在java中,執行下面這個語句

?
1
int i =12;

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

1 原子性

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

int i =12;

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

1.1 java中的原子性操作

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

例如:

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

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

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

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

2 可見性

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

2.1 可見性問題

例如:

?
1
2
3
4
5
6
//線程1
int i =12;
i=13;
 
//線程2
int j=i;

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

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

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

2.2 解決可見性問題

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

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

3 有序性

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

3.1 單個線程內程序的指令重排序

例如:

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

按照我們日常的思維,程序的執行過程是從上至下一行一行執行的,就是說按照代碼的順序來執行,那么JVM在實際中一定會這樣嗎??? 答案是否定的,這里可能會發生指令重排序(Instruction Reorder)。

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

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

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

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

3.2 多線程內程序的指令重排序

重排序不會影響單個線程內程序執行的結果,但是多線程就不一定了。

?
1
2
3
4
5
6
7
8
9
//線程1:
context = loadContext();   //語句1
inited = true;             //語句2
 
//線程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

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

3.3 保證有序性的解決方法

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

3.4 volatile 保證有序性的原理

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

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

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

4 實例分析

?
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 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//保證前面的線程都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

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

4.1 原理分析

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

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

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

4.2 synchronized 結合

?
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 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//保證前面的線程都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

4.3 Lock 結合

?
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
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//保證前面的線程都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

4.4 使用AtomicInteger替換int

?
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 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//保證前面的線程都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

到此這篇關于java并發編程之原子性、可見性、有序性 的文章就介紹到這了,更多相關java并發編程之原子性、可見性、有序性 內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.51cto.com/928343994/2841441

延伸 · 閱讀

精彩推薦
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
主站蜘蛛池模板: 色依依视频视频在线观看 | 成人午夜影院在线观看 | 国产国语在线播放视频 | 国产青草视频在线观看免费影院 | 欧美精品黑人巨大在线播放 | youjizzxxx69日本| 日本黄色网页 | 国产在线观看人成激情视频 | 狠狠综合网 | 久久精品人人做人人爽97 | 911亚洲精品国内自产 | 啊哈用力cao我 | 精品视频网站 | 小舞同人18av黄漫网站 | 国产性片在线观看 | 特级夫妻大片免费在线播放 | 色哟哟在线视频 | 射综合网 | 日本黄色网页 | 欧美日韩国产成人综合在线影院 | 国产伦码精品一区二区 | 亚洲美女aⅴ久久久91 | 国产精品亚洲一区二区久久 | 国产成人精品视频频 | 色老板在线免费视频 | 臀控福利大臀的网站 | 国产真实一区二区三区 | 亚洲欧美日韩国产精品影院 | 国产中文在线视频 | 啪啪免费网址 | 性色AV一区二区三区V视界影院 | 99ri国产在线观看 | 亚洲国产区男人本色在线观看欧美 | 91av爱爱 | 免费观看日本人成影片 | 3d美女触手怪爆羞羞漫画 | 日本免费播放 | 久久精品无码人妻无码AV蜜臀 | 无遮挡免费h肉动漫在线观看 | 精品一区二区三区视频日产 | 日产精品一卡2卡三卡4乱码久久 |