JavaRush /Java Blog /Random-TW /流量管理。易失性關鍵字和yield()方法

流量管理。易失性關鍵字和yield()方法

在 Random-TW 群組發布
你好!我們繼續研究多線程,今天我們將熟悉一個新的關鍵字— volatile 和yield() 方法。讓我們弄清楚它是什麼:)

關鍵字易失性

在創建多執行緒應用程式時,我們可能面臨兩個嚴重的問題。 首先,在多執行緒應用程式運行過程中,不同的執行緒可以快取變數的值(我們將在「使用 volatile」講座中詳細討論這一點)。有可能一個線程更改了變數的值,但第二個線程沒有看到此更改,因為它正在使用自己的變數快取副本。當然,後果可能很嚴重。想像一下,這不僅僅是某種“變數”,而是,例如,你的銀行卡餘額,突然開始隨機地來回跳躍:)不太令人愉快,對吧? 其次,在Java中,除了和之外的所有類型的欄位的讀寫操作都是原子的。longdouble 什麼是原子性?好吧,例如,如果您在一個線程中更改變數的值int,並且在另一個線程中讀取該變數的值,您將獲得其舊值或新值 - 更改後的值線程1. 那裡可能不會出現“中間選項”。但是,這不適用於long和。double為什麼?因為它是跨平台的。您還記得我們在第一級時說過的 Java 原則是「一次寫,隨處可用」嗎?這是跨平台的。也就是說,Java 應用程式運行在完全不同的平台上。例如,在Windows作業系統、不同版本的Linux或MacOS以及任何地方,該應用程式都可以穩定運作。 long- doubleJava 中最「重」的原語:它們有 64 位元。而有些32位元平台根本就沒有實現讀寫64位元變數的原子性。此類變數的讀取和寫入分兩次操作。首先,前 32 位元被寫入變量,然後是另外 32 位元。因此,在這些情況下可能會出現問題。一個線程將一些 64 位元值寫入變數Х,他「分兩步」完成。同時,第二個執行緒嘗試讀取該變數的值,並在中間執行此操作,此時前 32 位元已寫入,但第二個尚未寫入。結果,它讀取了一個不正確的中間值,並發生錯誤。例如,如果在這樣的平台上我們嘗試將數字寫入變數 - 9223372036854775809 - 它將佔用 64 位元。以二進位形式,它看起來像這樣: 100000000000000000000000000000000000000000000000000000000000000001 第一個將開始寫入此數字0 0000 00000 然後第二個32: 00000000000000000000000000000001 第二個執行緒可以楔入這個間隙並讀取變數的中間值 - 100000000000000000000000000000000,即已寫入的前 32 位元。在十進制中,這個數字等於2147483648。也就是說,我們只是想將數字9223372036854775809寫入變量,但由於這個操作在某些平台上不是原子的,所以我們得到了“左邊”的數字2147483648 ,我們不需要它,但它會如何影響程式的運作尚不清楚。第二個執行緒只是在最終寫入變數之前讀取該變數的值,也就是說,它看到了前 32 位,但看不到第二個 32 位。當然,這些問題並不是昨天才出現的,在 Java 中,這些問題只需使用一個關鍵字volatile就可以解決。如果我們在程式中聲明某個變量,並使用單字「易失性」...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…。代表著:
  1. 它將始終以原子方式讀取和寫入。即使它是 64 位元doublelong.
  2. Java機器不會快取它。因此排除了 10 個執行緒與其本機副本一起工作的情況。
這就是如何一言解決兩個非常嚴重的問題:)

yield() 方法

我們已經了解了該類別的許多方法Thread,但有一個重要的方法對您來說是新的。這就是yield()方法。從英文翻譯過來就是「屈服」。這正是該方法的作用! 流量管理。 volatile 關鍵字和yield() 方法 - 2當我們在一個線程上調用yield方法時,它實際上是在對其他線程說:“好吧,夥計們,我並不著急,所以如果對你們中的任何一個人來說獲得CPU時間很重要,那就接受吧,我很高興。”不緊急。” 這是一個簡單的例子來說明它是如何運作的:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + "give way to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
我們依次創建並啟動三個線程 - Thread-0Thread-1Thread-2Thread-0首先開始並立即讓位給其他人。在它開始之後Thread-1,它也讓路。之後就開始了Thread-2,這也是劣勢。我們沒有更多的線程了,在Thread-2最後一個線程讓出位置後,線程調度程序會查看:“那麼,沒有更多的新線程了,隊列中有誰呢?之前誰是最後一個讓出位置的Thread-2?我認為是Thread-1?好吧,那就這樣吧。” Thread-1完成它的工作直到結束,之後執行緒調度程序繼續協調:「好的,Thread-1 已經完成。我們還有其他人在排隊嗎?” 隊列中有 Thread-0:它在 Thread-1 之前放棄了自己的位置。現在事情已經到了他的面前,他正在被執行到底。之後調度程序完成線程協調:「好吧,Thread-2,你讓位給其他線程,它們都已經工作了。你是最後一個讓步的,所以現在輪到你了。” 此後,Thread-2 運行完成。控制台輸出將如下所示: Thread-0 讓位於其他執行緒 Thread-1 讓位於其他執行緒 Thread-2 讓位於其他執行緒 Thread-1 已完成執行。Thread-0 已完成執行。Thread-2 已完成執行。 當然,執行緒調度程序可以以不同的順序運行執行緒(例如,2-1-0 而不是 0-1-2),但原理是相同的。

發生在規則之前

今天我們要討論的最後一件事是「發生在之前」原則。如您所知,在 Java 中,為執行緒分配時間和資源以完成其任務的大部分工作都是由執行緒調度程序完成的。您還不止一次看到線程如何以任意順序執行,並且大多數情況下無法預測它。一般來說,在我們之前進行的「順序」程式設計之後,多執行緒看起來像是一個隨機的事情。正如您已經看到的,可以使用一整套方法來控制多執行緒程式的進度。但除此之外,Java 多執行緒中還有另一個「穩定島」——稱為「happens-before」的 4 條規則。從英語字面意思來看,這被翻譯為“happens before”或“happens before”。這些規則的意思很容易理解。想像一下我們有兩個線程 -AB。這些線程中的每一個都可以執行操作12。當在每個規則中我們說“ A發生在B之前”時,這意味著線程A在操作之前所做的所有更改以及該操作所帶來的更改在執行操作時1對線程可見,並且執行操作後。這些規則中的每一個都確保在編寫多執行緒程式時,某些事件在 100% 的時間裡會先於其他事件發生,並且操作時的執行緒將始終知道該執行緒在操作期間所做的變更。讓我們看看它們。 B2B2А1

規則1。

釋放互斥體發生在另一個執行緒取得相同監視器之前。嗯,這裡一切似乎都很清楚了。如果某個物件或類別的互斥量被一個執行緒(例如 一個 thread )獲取,則А另一個執行緒( thread B)不能同時取得它。您需要等待直到互斥體被釋放。

規則 2.

發生在方法Thread.start() 之前 Thread.run()。也沒什麼複雜的。您已經知道:為了讓方法內的程式碼開始執行run(),您需要在執行緒上呼叫該方法start()。這是祂的,而不是方法本身run()!此規則可確保Thread.start()執行前設定的所有變數的值在開始執行的方法內部可見run()

規則 3.

方法完成run() 發生在方法退出之前join()。讓我們回到我們的兩個流 -АB。我們以這樣的方式呼叫該方法join(),即執行緒B必須等到完成A才能執行其工作。這意味著run()對象A的方法肯定會運行到最後。當執行緒等待完成並開始工作 時,run()執行緒方法中發生的所有資料變更都A將在執行緒中完全可見。BA

規則 4.

寫入易失性變數發生在讀取相同變數之前。事實上,透過使用 volatile 關鍵字,我們將始終獲得當前值。long即使在和的情況下double,其問題已在前面討論過。正如您已經了解的那樣,某些線程中所做的更改並不總是對其他線程可見。但是,當然,在許多情況下,這種程序行為並不適合我們。假設我們為線程中的變數賦值A
int z;.

z= 555;
如果我們的執行緒B要將變數的值印z到控制台,它很容易列印 0,因為它不知道分配給它的值。所以,規則 4 向我們保證:如果你將變數宣告z為 volatile,那麼在一個執行緒中對其值的變更將始終在另一個執行緒中可見。如果我們在前面的程式碼中加入「易失性」一詞...
volatile int z;.

z= 555;
B....排除流向控制台輸出 0 的情況。寫入易失性變數發生在讀取之前。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION