CodeGym /Java Blog /Toto sisi /管理線程。volatile 關鍵字和 yield() 方法
John Squirrels
等級 41
San Francisco

管理線程。volatile 關鍵字和 yield() 方法

在 Toto sisi 群組發布
你好!我們繼續研究多線程。今天我們將了解volatile關鍵字和yield()方法。讓我們潛入:)

volatile 關鍵字

創建多線程應用程序時,我們會遇到兩個嚴重的問題。 首先,當多線程應用程序運行時,不同的線程可以緩存變量的值(我們已經在標題為“使用 volatile”的課程中討論過這個)。您可能會遇到這樣的情況,其中一個線程更改了變量的值,但第二個線程看不到更改,因為它正在使用變量的緩存副本。 自然,後果可能很嚴重。假設它不僅僅是任何舊變量,而是您的銀行賬戶餘額,它突然開始隨機地上下跳躍:) 這聽起來並不有趣,對吧? 其次,在 Java 中,讀寫所有原始類型的操作,longdouble, 是原子的。 好吧,例如,如果您int在一個線程上更改變量的值,而在另一個線程上讀取該變量的值,您將獲得它的舊值或新值,即更改產生的值在線程 1 中。沒有“中間值”。但是,這不適用於longs 和doubles。為什麼? 因為跨平台支持。 還記得我們在開始階段說過 Java 的指導原則是“一次編寫,隨處運行”嗎?這意味著跨平台支持。換句話說,Java 應用程序可以運行在各種不同的平台上。例如,在 Windows 操作系統、不同版本的 Linux 或 MacOS 上。它將在所有這些上順利運行。稱重 64 位,longdouble是 Java 中“最重”的原語。並且某些 32 位平台根本不實現 64 位變量的原子讀寫。此類變量在兩個操作中讀取和寫入。首先,將前 32 位寫入變量,然後再寫入另外 32 位。結果,可能出現問題。一個線程將一些 64 位值寫入一個X變量,並在兩個操作中完成。同時,第二個線程嘗試讀取變量的值,並在這兩個操作之間執行 - 當前 32 位已寫入但後 32 位尚未寫入時。結果,它讀取了一個中間的、不正確的值,我們遇到了一個錯誤。例如,如果在這樣的平台上我們嘗試將數字寫入 9223372036854775809 對於一個變量,它將佔用 64 位。在二進制形式中,它看起來像這樣: 10000000000000000000000000000000000000000000000000000000000000001 第一個線程開始將數字寫入變量。首先,它寫入前 32 位 (1000000000000000000000000000000) ,然後寫入第二個 32 位 (00000000000000000000000000000001) 第二個線程可以插入這些操作之間,讀取變量的中間值 (1000000000000000000000000000000),這是已經寫入的前 32 位。在十進制中,這個數字是 2,147,483,648。換句話說,我們只是想將數字 9223372036854775809 寫入一個變量,但由於這個操作在某些平台上不是原子的,我們有一個邪惡的數字 2,147,483,648,它不知從哪裡冒出來,會對程序。第二個線程在完成寫入之前簡單地讀取變量的值,即線程看到前 32 位,但看不到後 32 位。當然,這些問題不是昨天出現的。Java 用一個關鍵字解決了它們:volatile. 如果我們使用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在一個線程上調用該方法時,它實際上會與其他線程對話:'嘿,伙計們。我並不特別著急去任何地方,所以如果對你們中的任何人來說獲得處理器時間很重要,那就拿去吧——我可以等。這是一個簡單的例子,說明它是如何工作的:

public class ThreadExample extends Thread {

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

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place 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),但原理是一樣的。

happens-before 規則

我們今天要談的最後一件事是“發生在之前”的概念。如您所知,在 Java 中,線程調度程序執行涉及為線程分配時間和資源以執行其任務的大部分工作。您還反复看到線程如何以通常無法預測的隨機順序執行。通常,在我們之前進行的“順序”編程之後,多線程編程看起來像是隨機的。您已經開始相信可以使用許多方法來控制多線程程序的流程。但是 Java 中的多線程還有一個支柱 — 4 個“ happens-before ”規則。理解這些規則非常簡單。想像一下,我們有兩個線程——A並且B. 這些線程中的每一個都可以執行操作12。在每個規則中,當我們說“ A happens-before BA ”時,我們的意思是線程在操作之前所做的所有更改以及此操作導致的更改在執行操作時和之後1對線程可見。每條規則保證當你編寫一個多線程程序時,某些事件將在 100% 的時間內發生在其他事件之前,並且在運行時線程將始終知道該線程在運行期間所做的更改。讓我們回顧一下。 B22BA1

規則1。

釋放互斥量發生在同一監視器被另一個線程獲取之前。我想你明白這裡的一切。如果一個對像或類的互斥鎖被一個線程獲取,例如被線程獲取,則A另一個線程 (thread B) 無法同時獲取它。它必須等到互斥量被釋放。

規則 2。

Thread.start()方法發生在 Thread.run(). 同樣,這裡沒有什麼困難。您已經知道要開始運行方法內的代碼,您必須在線程上run()調用該方法。start()具體來說,是啟動方法,而不是run()方法本身!此規則確保在調用之前設置的所有變量的值在方法開始後將在方法 Thread.start()內可見。run()

規則 3。

run()方法結束髮生在方法返回之前join()。讓我們回到我們的兩個線程:AB。我們調用join()該方法以確保線程在執行其工作之前B等待線程完成。A這意味著 A 對象的run()方法保證運行到最後。run()並且線程方法中發生的所有數據更改A都將 100% 保證在線程B完成後在線程中可見,等待線程A完成其工作以便它可以開始自己的工作。

規則 4。

寫入volatile變量發生在從同一變量讀取之前。當我們使用volatile關鍵字時,我們實際上總是得到當前值。即使是longor double(我們之前談到過這裡可能發生的問題)。 正如您已經了解的那樣,對某些線程所做的更改並不總是對其他線程可見。但是,當然,這種行為不適合我們的情況非常常見。假設我們給線程上的一個變量賦值A

int z;

….

z = 555;
如果我們的B線程應該在控制台上顯示變量的值z,它很容易顯示 0,因為它不知道分配的值。但是規則 4 保證,如果我們將變量聲明zvolatile,那麼在一個線程上對其值的更改將始終在另一個線程上可見。如果我們將單詞添加volatile到之前的代碼中......

volatile int z;

….

z = 555;
...然後我們防止線程可能顯示 0 的情況。B寫入volatile變量發生在讀取變量之前。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION