文章目錄
- 一、happens-before的定義
- 二、happens-before的規則
- 1. 程序順序規則:
- 2. 監視器鎖規則:
- 3. volatile變量規則:
- 4. 傳遞性:
- 5. start()規則:
- 6. join()規則:
一、happens-before的定義
- 如果一個操作happens-before另一個操作,那么第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
- 兩個操作之間存在happens-before關系,并不意味著Java平臺的具體實現必須要按照 happens-before關系指定的順序來執行。如果重排序之后的執行結果,與按happens-before關系來執行的結果一致,那么這種重排序并不非法
二、happens-before的規則
1. 程序順序規則:
一個線程中的每個操作,happens-before于該線程中的任意后續操作。
程序順序規則(Program Order Rule)指的是在單個線程內,按照程序的順序,前面的操作 happens-before 后面的操作。這意味著一個線程中的每個操作都會在該線程中的任意后續操作之前發生。
下面是一個簡單的Java程序示例,展示了程序順序規則的應用:
public class ProgramOrderDemo {private int count = 0;public void increment() {count++; // 操作1}public void printCount() {System.out.println("Count: " + count); // 操作2}public static void main(String[] args) {ProgramOrderDemo demo = new ProgramOrderDemo();Thread thread1 = new Thread(() -> {demo.increment(); // 線程1中的操作1demo.printCount(); // 線程1中的操作2});Thread thread2 = new Thread(() -> {demo.increment(); // 線程2中的操作1demo.printCount(); // 線程2中的操作2});thread1.start();thread2.start();}
}
在上述示例中,我們創建了兩個線程:thread1和thread2,它們都會調用ProgramOrderDemo類中的increment()和printCount()方法。
根據程序順序規則,線程1中的操作1(count++)happens-before 線程1中的操作2(System.out.println("Count: " + count)),同樣線程2中的操作1(count++)happens-before 線程2中的操作2(System.out.println("Count: " + count))。
這意味著在每個線程中,count++操作一定會在System.out.println("Count: " + count)操作之前發生。因此,我們可以確保在每個線程中打印的count值是遞增的。
請注意,盡管兩個線程并行執行,但由于程序順序規則的存在,各個線程內部的操作順序是有序的,不會出現線程間的競爭條件。
2. 監視器鎖規則:
對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。
監視器鎖規則(Monitor Lock Rule)指的是對一個鎖的解鎖操作 happens-before 于隨后對同一個鎖的加鎖操作。這個規則確保了在多線程環境下,對共享資源的同步訪問。
下面是一個簡單的Java程序示例,展示了監視器鎖規則的應用:
public class MonitorLockDemo {private int count = 0;private Object lock = new Object();public void increment() {synchronized (lock) {count++; // 線程1中的操作1}}public void printCount() {synchronized (lock) {System.out.println("Count: " + count); // 線程2中的操作2}}public static void main(String[] args) {MonitorLockDemo demo = new MonitorLockDemo();Thread thread1 = new Thread(() -> {demo.increment(); // 線程1中的操作1});Thread thread2 = new Thread(() -> {demo.printCount(); // 線程2中的操作2});thread1.start();thread2.start();}
}
在上述示例中,我們創建了兩個線程:thread1和thread2,它們都會調用MonitorLockDemo類中的increment()和printCount()方法。在這個類中,我們使用了一個對象lock作為鎖。
根據監視器鎖規則,線程1中的操作1(count++)happens-before 線程2中的操作2(System.out.println("Count: " + count))。
這意味著在線程1中,對鎖的解鎖操作一定會在線程2中對同一個鎖的加鎖操作之前發生。因此,我們可以確保在執行打印操作時,count的值是線程1已經更新過的。
請注意,通過使用synchronized關鍵字和共享的鎖對象,我們確保了對count的訪問是同步的,避免了競態條件和數據不一致的問題。這是因為監視器鎖規則確保了解鎖操作 happens-before 加鎖操作,從而確保了對共享資源的正確同步訪問。
3. volatile變量規則:
對一個volatile域的寫,happens-before于任意后續對這個volatile域的讀。
volatile變量規則(Volatile Variable Rule)指的是對一個volatile域的寫操作 happens-before 于任意后續對這個volatile域的讀操作。這個規則確保了在多線程環境下,對volatile變量的可見性和順序性。
下面是一個簡單的Java程序示例,展示了volatile變量規則的應用:
public class VolatileVariableDemo {private volatile int number = 0;public void writeNumber() {number = 42; // 寫操作}public void readNumber() {System.out.println("Number: " + number); // 讀操作}public static void main(String[] args) {VolatileVariableDemo demo = new VolatileVariableDemo();Thread thread1 = new Thread(() -> {demo.writeNumber(); // 寫操作});Thread thread2 = new Thread(() -> {demo.readNumber(); // 讀操作});thread1.start();thread2.start();}
}
在上述示例中,我們創建了兩個線程:thread1和thread2,它們都會調用VolatileVariableDemo類中的writeNumber()和readNumber()方法。在這個類中,我們使用了一個volatile修飾的變量number。
根據volatile變量規則,線程1中的寫操作(number = 42)happens-before 線程2中的讀操作(System.out.println("Number: " + number))。
這意味著在線程1中,對number的寫操作一定會在線程2中對同一個number的讀操作之前發生。因此,我們可以確保在執行打印操作時,讀取到的number的值是線程1已經寫入的。
請注意,使用volatile修飾變量可以保證其在多線程環境下的可見性和順序性。這意味著對volatile變量的寫操作對其他線程是可見的,并且讀操作一定會讀取到最新的值。這是因為volatile變量規則確保了對volatile變量的寫 happens-before 于任意后續對這個volatile變量的讀。
4. 傳遞性:
如果A happens-before B,且B happens-before C,那么A happens-before C。
下面是一個新的示例,展示了happens-before關系的傳遞性:
public class TransitivityDemo {private int number = 0;private volatile boolean ready = false;public void writer() {number = 42; // 寫操作ready = true; // 寫操作}public void reader() {if (ready) { // 讀操作System.out.println("Number: " + number); // 讀操作}}public static void main(String[] args) {TransitivityDemo demo = new TransitivityDemo();Thread thread1 = new Thread(() -> {demo.writer(); // 寫操作});Thread thread2 = new Thread(() -> {demo.reader(); // 讀操作});thread1.start();thread2.start();}
}
在這個示例中,我們有一個TransitivityDemo類,其中包含一個number變量和一個ready變量。在writer()方法中,我們首先進行寫操作number = 42,然后進行寫操作ready = true。在reader()方法中,我們進行讀操作if (ready),如果ready為true,則進行讀操作System.out.println("Number: " + number)。
根據happens-before關系的傳遞性,線程1中的寫操作number = 42 happens-before 線程1中的寫操作ready = true。同時,線程1中的寫操作ready = true happens-before 線程2中的讀操作if (ready)。因此,我們可以得出結論,線程1中的寫操作number = 42 happens-before 線程2中的讀操作if (ready)。
由于happens-before關系的傳遞性,我們可以得出A happens-before C的結論,即線程1中的寫操作number = 42 happens-before 線程2中的讀操作System.out.println("Number: " + number)。
這個示例演示了happens-before關系的傳遞性,其中A happens-before B,B happens-before C,因此A happens-before C。
5. start()規則:
如果線程A執行操作ThreadB.start()(啟動線程B),那么A線程的 ThreadB.start()操作happens-before于線程B中的任意操作。
當線程A執行ThreadB.start()方法啟動線程B時,根據Java內存模型(Java Memory Model,JMM)中的start()規則,線程A的ThreadB.start()操作將happens-before于線程B中的任意操作。這意味著線程B中的任意操作都可以看到線程A在調用ThreadB.start()之前的操作。
下面是一個簡單的示例代碼來展示這個規則:
public class ThreadDemo {private static boolean ready = false;public static void main(String[] args) throws InterruptedException {ThreadB threadB = new ThreadB();Thread threadA = new Thread(() -> {System.out.println("Thread A is doing some work");ready = true;threadB.start(); // 線程A啟動線程B});threadA.start(); // 啟動線程AthreadA.join(); // 等待線程A執行完畢System.out.println("Thread B is ready? " + threadB.isReady());}static class ThreadB extends Thread {public boolean isReady() {return ready;}@Overridepublic void run() {System.out.println("Thread B is running");// 在這里可以看到線程A在調用ThreadB.start()之前的操作System.out.println("Thread B sees ready? " + ready);}}
}
在這個示例中,線程A首先會執行一些工作,并將ready屬性設置為true。然后,線程A調用threadB.start()來啟動線程B。在線程B的run()方法中,我們可以看到線程A在調用ThreadB.start()之前的操作,即輸出Thread B sees ready? true。
因此,根據start()規則,線程A的ThreadB.start()操作happens-before于線程B中的任意操作,確保了對ready屬性的修改對線程B可見。
運行結果如下:
6. join()規則:
如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作 happens-before于線程A從ThreadB.join()操作成功返回。
根據Java內存模型(Java Memory Model,JMM)中的join()規則,如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。這意味著線程A能夠看到線程B在join()之前的操作。
下面是一個簡單的示例代碼來展示這個規則:
public class ThreadDemo {private static boolean ready = false;public static void main(String[] args) throws InterruptedException {ThreadB threadB = new ThreadB();Thread threadA = new Thread(() -> {System.out.println("Thread A is doing some work");threadB.start(); // 線程A啟動線程Btry {threadB.join(); // 線程A等待線程B執行完畢} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread A sees ready? " + ready);});threadA.start(); // 啟動線程AthreadA.join(); // 等待線程A執行完畢System.out.println("Thread B is ready? " + threadB.isReady());}static class ThreadB extends Thread {public boolean isReady() {return ready;}@Overridepublic void run() {System.out.println("Thread B is running");ready = true; // 在這里修改ready屬性}}
}
在這個示例中,線程A首先會執行一些工作,并啟動線程B。然后,線程A調用threadB.join()來等待線程B執行完畢。在線程B的run()方法中,我們將ready屬性設置為true。
當線程A從threadB.join()成功返回后,它能夠看到線程B在join()之前的操作。因此,線程A輸出的Thread A sees ready? true將顯示線程B在join()之前將ready屬性設置為true。
因此,根據join()規則,線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回,確保了對ready屬性的修改對線程A可見。
運行結果: