日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java多线程:易失性变量,事前关联和内存一致性

發布時間:2023/12/3 java 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程:易失性变量,事前关联和内存一致性 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

什么是volatile變量?

volatile是Java中的關鍵字。 您不能將其用作變量或方法名稱。 期。

我們什么時候應該使用它?

哈哈,對不起,沒辦法。

當我們在多線程環境中與多個線程共享變量時,我們通常使用volatile關鍵字,并且我們希望避免由于這些變量在CPU緩存中的緩存而導致任何內存不一致錯誤 。

考慮下面的生產者/消費者示例,其中我們一次生產/消費一件商品:

public class ProducerConsumer {private String value = "";private boolean hasValue = false;public void produce(String value) {while (hasValue) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Producing " + value + " as the next consumable");this.value = value;hasValue = true;}public String consume() {while (!hasValue) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}String value = this.value;hasValue = false;System.out.println("Consumed " + value);return value;} }

在上述類中, Produce方法通過將其參數存儲到value中并將hasValue標志更改為true來生成一個新值。 while循環檢查值標志( hasValue )是否為true,這表示存在尚未使用的新值,如果為true,則請求當前線程進入睡眠狀態。 僅當hasValue標志已更改為false時,此睡眠循環才會停止,這只有在consumer方法使用了新值的情況下才有可能。 如果沒有新值可用,那么消耗方法將請求當前線程休眠。 當Produce方法產生一個新值時,它將終止其睡眠循環,使用它并清除value標志。

現在想象一下,有兩個線程正在使用此類的對象–一個正在嘗試產生值(寫線程),另一個正在使用它們(讀線程)。 以下測試說明了這種方法:

public class ProducerConsumerTest {@Testpublic void testProduceConsume() throws InterruptedException {ProducerConsumer producerConsumer = new ProducerConsumer();List<String> values = Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8","9", "10", "11", "12", "13");Thread writerThread = new Thread(() -> values.stream().forEach(producerConsumer::produce));Thread readerThread = new Thread(() -> {for (int i = 0; i > values.size(); i++) {producerConsumer.consume();}});writerThread.start();readerThread.start();writerThread.join();readerThread.join();} }

該示例在大多數情況下將產生預期的輸出,但也很有可能陷入僵局!

怎么樣?

讓我們再談一下計算機體系結構。

我們知道一臺計算機由CPU和內存單元(以及許多其他部件)組成。 即使主存儲器是我們所有程序指令和變量/數據所在的位置,在程序執行期間,CPU仍可以將變量的副本存儲在其內部存儲器(稱為CPU緩存)中,以提高性能。 由于現代計算機現在具有不止一個CPU,因此也有不止一個CPU緩存。

在多線程環境中,可能有多個線程同時執行,每個線程都在不同的CPU中運行(盡管這完全取決于基礎操作系統),并且每個線程都可以從main復制變量。內存放入相應的CPU緩存中。 當線程訪問這些變量時,它們隨后將訪問這些緩存的副本,而不是主內存中的實際副本。

現在,假設測試中的兩個線程在兩個不同的CPU上運行,并且hasValue標志已緩存在其中一個(或兩個)上。 現在考慮以下執行順序:

  • writerThread產生一個值,并將hasValue更改為true。 但是,此更新僅反映在緩存中,而不反映在主存儲器中。
  • readerThread嘗試使用一個值,但是hasValue標志的緩存副本設置為false。 因此,即使writerThread產生了一個值,它也無法使用它,因為線程無法脫離睡眠循環( hasValue為false)。
  • 由于readerThread沒有使用新生成的值, writerThread不能繼續進行,因為該標志沒有被清除,因此它將停留在其休眠循環中。
  • 而且我們手中有一個僵局!
  • 僅當hasValue標志跨所有緩存同步時,這種情況才會改變,這完全取決于基礎操作系統。

    volatile如何適合此示例?

    如果僅將hasValue標志標記為volatile ,則可以確保不會發生這種類型的死鎖:

    private volatile boolean hasValue = false;

    將變量標記為volatile將強制每個線程直接從主內存讀取該變量的值。 而且,每次對volatile變量的寫操作都會立即刷新到主存儲器中。 如果線程決定緩存該變量,則它將在每次讀/寫時與主內存同步。

    進行此更改后,請考慮導致死鎖的先前執行步驟:

  • 作家線程 ? 產生一個值,并將hasValue更改為true。 這次更新將直接反映到主內存中(即使已緩存)。
  • 讀取器線程正在嘗試使用一個值,并檢查hasValue的值。 這次,每次讀取將強制直接從主內存中獲取值,因此它將獲取寫入線程所做的更改。
  • 閱讀器線程使用生成的值,并清除標志的值。 這個新值將進入主內存(如果已緩存,則緩存的副本也將被更新)。
  • 編寫器線程將接受此更改,因為每個讀取現在都在訪問主內存。 它將繼續產生新的價值。
  • 瞧! 我們都很高興^ _ ^!

    這是所有易失性行為,迫使線程直接從內存中讀取/寫入變量嗎?

    實際上,它還具有其他含義。 訪問易失性變量會在程序語句之間建立先發生后關系。

    什么是

    兩個程序語句之間的先發生后關系是一種保證,可以確保一個語句寫的任何內存對另一條語句可見。

    它與

    當我們寫入一個volatile變量時,它會在以后每次讀取該相同變量時創建一個事前發生的關系。 因此,直到對該易失性變量進行寫操作之前完成的所有內存寫操作,對于該易失性變量的讀取之后的所有語句,隨后都將可見。

    Err….Ok…。我明白了,但也許是一個很好的例子。

    好的,對模糊的定義表示抱歉。 考慮以下示例:

    // Definition: Some variables private int first = 1; private int second = 2; private int third = 3; private volatile boolean hasValue = false;// First Snippet: A sequence of write operations being executed by Thread 1 first = 5; second = 6; third = 7; hasValue = true;// Second Snippet: A sequence of read operations being executed by Thread 2 System.out.println("Flag is set to : " + hasValue); System.out.println("First: " + first); // will print 5 System.out.println("Second: " + second); // will print 6 System.out.println("Third: " + third); // will print 7

    假設上面的兩個代碼片段由兩個不同的線程(線程1和2)執行。當第一個線程更改hasValue時 ,它不僅會將此更改刷新到主內存,還將導致前三個寫操作(以及其他任何寫操作)先前的寫入)也要刷新到主存儲器中! 結果,當第二個線程訪問這三個變量時,它將看到線程1進行的所有寫操作,即使它們之前都已被緩存(這些緩存的副本也將被更新)!

    這就是為什么我們在第一個示例中也不必用volatile標記值變量的原因。 由于我們在訪問hasValue之前已寫入該變量,并且在讀取hasValue之后已從該變量讀取,因此該變量會自動與主內存同步。

    這還有另一個有趣的結果。 JVM以其程序優化而聞名。 有時,它會重新排列程序語句以提高性能,而不會更改程序的輸出。 例如,它可以更改以下語句序列:

    first = 5; second = 6; third = 7;

    到這個:

    second = 6; third = 7; first = 5;

    但是,當語句涉及訪問volatile變量時,它將永遠不會移動發生在volatile寫入之后的語句。 這意味著它將永遠不會改變:

    first = 5; // write before volatile write second = 6; // write before volatile write third = 7; // write before volatile write hasValue = true;

    到這個:

    first = 5; second = 6; hasValue = true; third = 7; // Order changed to appear after volatile write! This will never happen!

    即使從程序正確性的角度來看,它們似乎都是等效的。 請注意,只要它們都出現在易失性寫入之前,仍然允許JVM重新排序其中的前三個寫入。

    同樣,JVM也不會更改在讀取易失性變量后出現在訪問之前的語句的順序。 這意味著:

    System.out.println("Flag is set to : " + hasValue); // volatile read System.out.println("First: " + first); // Read after volatile read System.out.println("Second: " + second); // Read after volatile read System.out.println("Third: " + third); // Read after volatile read

    JVM絕不會將其轉換為:

    System.out.println("First: " + first); // Read before volatile read! Will never happen! System.out.println("Fiag is set to : " + hasValue); // volatile read System.out.println("Second: " + second); System.out.println("Third: " + third);

    但是,JVM可以肯定它們中最后三個讀取的順序,只要它們在可變讀取之后一直出現。

    我認為必須為易失性變量付出性能損失。

    您說對了,因為易失性變量會強制訪問主內存,并且訪問主內存總是比訪問CPU緩存慢。 它還會阻止JVM對某些程序進行優化,從而進一步降低性能。

    我們是否可以始終使用易變變量來維護線程之間的數據一致性?

    不幸的是沒有。 當有多個線程讀寫同一變量時,將其標記為volatile不足以保持一致性。 考慮以下UnsafeCounter類:

    public class UnsafeCounter {private volatile int counter;public void inc() {counter++;}public void dec() {counter--;}public int get() {return counter;} }

    和以下測試:

    public class UnsafeCounterTest {@Testpublic void testUnsafeCounter() throws InterruptedException {UnsafeCounter unsafeCounter = new UnsafeCounter();Thread first = new Thread(() -> {for (int i = 0; i < 5; i++) { unsafeCounter.inc();}});Thread second = new Thread(() -> {for (int i = 0; i < 5; i++) {unsafeCounter.dec();}});first.start();second.start();first.join();second.join();System.out.println("Current counter value: " + unsafeCounter.get());} }

    該代碼非常不言自明。 我們在一個線程中增加計數器,而在另一個線程中減少相同次數。 運行此測試后,我們希望計數器保持0,但這不能保證。 在大多數情況下,它將為0,在某些情況下,它將為-1,-2、1、2,即[-5,5]范圍內的任何整數值。

    為什么會這樣? 發生這種情況是因為計數器的遞增和遞減操作都不是原子的-它們不會一次全部發生。 它們都由多個步驟組成,并且步驟順序相互重疊。 因此,您可以考慮以下增量操作:

  • 讀取計數器的值。
  • 添加一個。
  • 寫回計數器的新值。
  • 遞減操作如下:

  • 讀取計數器的值。
  • 從中減去一個。
  • 寫回計數器的新值。
  • 現在,讓我們考慮以下執行步驟:

  • 第一個線程已從內存中讀取計數器的值。 最初將其設置為零。 然后向其中添加一個。
  • 第二個線程還從內存中讀取了計數器的值,并看到將其設置為零。 然后從中減去一個。
  • 現在,第一個線程將counter的新值寫回內存,將其更改為1。
  • 現在,第二個線程將計數器的新值寫回內存,即-1。
  • 第一線程的更新丟失。
  • 我們如何防止這種情況?

    通過使用同步:

    public class SynchronizedCounter {private int counter;public synchronized void inc() {counter++;}public synchronized void dec() {counter--;}public synchronized int get() {return counter;} }

    或使用AtomicInteger :

    public class AtomicCounter {private AtomicInteger atomicInteger = new AtomicInteger();public void inc() {atomicInteger.incrementAndGet();}public void dec() {atomicInteger.decrementAndGet();}public int get() {return atomicInteger.intValue();} }

    我個人的選擇是使用AtomicInteger作為同步對象,因為只有一個線程可以訪問任何inc / dec / get方法,從而大大降低了性能。

    意思是不是……..?

    對。 使用synced關鍵字還可以建立語句之間的事前發生關系。 輸入同步的方法/塊將在它之前出現的語句與該方法/塊內部的語句之間建立先發生后關系。 有關建立事前關系的完整列表,請轉到此處 。

    就暫時而言,這就是我要說的。

    • 所有示例都已上傳到我的github存儲庫中 。

    翻譯自: https://www.javacodegeeks.com/2015/11/java-multi-threading-volatile-variables-happens-before-relationship-and-memory-consistency.html

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的Java多线程:易失性变量,事前关联和内存一致性的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 99热热| 久热这里只有 | 一级女人毛片 | 91精品人妻一区二区三区 | 国产裸体美女永久免费无遮挡 | 一区二区不卡 | 狠狠干狠狠撸 | 美女扒开腿让男人操 | 中国少妇无码专区 | 韩国一级一片高清免费观看 | 打屁股疼的撕心裂肺的视频 | 亚洲国产精品一区二区尤物区 | 九九热在线免费视频 | 曰本无码人妻丰满熟妇啪啪 | 艳母免费在线观看 | 香蕉尹人网 | 亚洲第一色视频 | 极品白嫩丰满少妇无套 | 偷拍老头老太高潮抽搐 | 红杏出墙记 | 精品亚洲一区二区 | 亚洲婷婷小说 | 婷婷综合激情网 | 男人视频网 | 亚州av | 日韩亚洲精品在线 | 免费毛片看 | av色站 | 国产香蕉视频 | 凹凸精品一区二区三区 | 动漫同人高h啪啪爽文 | 天天弄天天干 | 男女黄床上色视频免费的软件 | 私人av| 在线观看日韩欧美 | 91蜜桃在线观看 | 久青草影院 | 中文字幕视频一区二区 | 中文字幕高清在线免费播放 | 久久成年人 | 国产激情自拍视频 | av青青草 | 久久久精品人妻一区二区三区四 | 欧美午夜精品久久久久久浪潮 | 嫩草视频国产 | 曰批女人视频在线观看 | 国产在线视频91 | 一本色道久久综合亚洲精品小说 | 无套暴操 | 午夜激情久久 | 五月天开心网 | 午夜精品一区二区三区在线播放 | 日本男人天堂网 | 国产911在线观看 | 激情小说av | 精品乱子伦一区二区 | 视频国产一区 | av大帝在线观看 | 爱爱视频网站 | 亚洲综合欧美综合 | 91视频第一页 | 激情综合网五月婷婷 | xxxx日韩| 国产激情视频在线观看 | 国产午夜精品久久久久 | 69视频在线观看 | 一起射导航 | 激情亚洲天堂 | 日日射av | 日韩欧美啪啪 | 黑人添美女bbb添高潮了 | 亚州春色 | 欧美在线一级视频 | 狠狠干天天色 | 成人国产一区二区三区 | 亚洲黄色精品视频 | 最新日韩在线视频 | 国产精品二区一区二区aⅴ污介绍 | 欧美a在线播放 | 亚洲中字 | 国产经典一区二区 | youjizz.com国产 | 播放毛片| 久久精品亚洲 | 日韩精品一区二区三区丰满 | 日韩av高清无码 | 成人精品三级av在线看 | 国产精品www | av资源网址 | 18久久久| 日韩精品久久久久久免费 | 欧美精品一二三 | 人妻精品久久久久中文字幕69 | 美女av毛片| 少妇裸体淫交视频免费看高清 | 国产一级片免费看 | 国产精品亚洲一区二区三区 | 日本3p视频 | 无套内谢老熟女 |