Memory Consistency Errors解決方案:Happens-before relationship

在這裏插入圖片描述

一、JMM在多線程環境下的存在的問題之一:Memory Consistency Errors

  • Memory Consistency Errors:Memory consistency errors occur when different threads have inconsistent views of what should be the same data. Fortunately, the programmer does not need a detailed understanding of these causes. All that is needed is a strategy for avoiding them.

The key to avoiding Memory Consistency Errors is understanding the happens-before relationship.

二、what is Happens-before relationship

Happens-before relationship is a guarantee that action performed by one thread is visible to another action in different thread.

Happens-before defines a partial ordering on all actions within the program. To guarantee that the thread executing action Y can see the results of action X (whether or not X and Y occur in different threads), there must be a happens-before relationship between X and Y. In the absence(缺失) of a happens-before ordering between two operations, the JVM is free to reorder them as it wants.(JIT compiler optimization)
爲了提升性能,對操作進行重排序。爲了保證特定(X、Y)操作順序,Happens-before定義了這部分操作順序。

Happens-before is not reordering of actions in ‘time’ but a guarantee of ordering of read and write to memory . Two threads performing write and read to memory can be consistent to each other actions in terms of clock time but might not see each others changes consistently (Memory Consistency Errors) unless they have happens-before relationship.
由於指令重排序的存在,兩個操作之間有happen-before關係,不是前一個操作必須要在後一個操作之前執行,而是前一個操作的執行結果對於後一個操作是可見的,並且前一個操作按順序排在第二個操作之前。

簡單理解:X Happens-before Y 就是X對共享變量的操作對Y讀此共享變量是可見的

三、How to establish happens-before relation

Followings are the rules for happens-before:

  • Single thread rule(程序員順序規則): Each action in a single thread happens-before every action in that thread that comes later in the program order. 在一個線程中,在程序前面的操作happens-before後面的操作。
    這也是爲什麼程序按照我們寫的順序執行的原因。

在這裏插入圖片描述

  • Monitor lock rule(監視器鎖規則): An unlock on a monitor lock (exiting synchronized method/block) happens-before every subsequent acquiring on the same monitor lock.
    管程是一種通用的同步語句,在Java中主要指的就是Synchronized。
    一個 unlock(解鎖) 操作happens-before後面對同一個鎖的 lock(加鎖) 操作。
    簡單來講:如果線程1解鎖了monitor a,接着線程2鎖定了a,那麼,線程1解鎖a之前的寫操作都對線程2可見

synchronized作用:①互斥訪問臨界區 ②保證對變量操作相對其他線程可見性

在這裏插入圖片描述
舉例:若線程A、B同時訪問read方法,當線程A執行完之後,線程B能夠獲取到線程A對變量的操作。

public class HappensBefore {

    int value = 10;

    public void read(){
        synchronized (this){ //此處加鎖
            if(value < 100){
                value ++;
            }
        }//此處自動解鎖
    }
}
  • Volatile variable rule: A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors (synchronized block around reads and writes), but without actually aquiring monitors/locks.
    對 volatile進行寫操作 happens-before 後續讀取同一字段。 volatile 的寫入和讀取與進入和退出監視器(讀取和寫入周圍的同步塊)具有相似的內存一致性效果,但實際上並不需要獲取監視器/鎖。

簡單來說:如果線程1寫入了volatile變量v,接着線程2讀取了v,那麼,線程1寫入v及之前的寫操作都對線程2可見
在這裏插入圖片描述

  • Thread start rule: A call to Thread.start() on a thread happens-before every action in the started thread. Say thread A spawns(引發) a new thread B by calling threadA.start(). All actions performed in thread B’s run method will see thread A’s calling threadA.start() method and before that (only in thread A) happened before them.
    【spawn:If something spawns something else, it causes it to happen or to be created. 引發】
    A 線程中調用 B 線程的 start(), 則 start() 操作 happen before 於 B 線程所有操作,也就是說 A 線程調用 start() 操作前對共享變量的賦值,對 B 線程可見,即在 B 線程中能獲取 A 對到共享變量操作後的新值。【子線程能看見主線程對共享變量的操作】
    在這裏插入圖片描述
static int x = 0;

public static void main(String[] args) {
    x = 1;
    Thread t = new Thread() {
        public void run() {
            int y = x;
        };
    };
    t.start();
}

The main thread has changed field x. Java memory model does not guarantee that this change will be visible to other threads if they are not synchronized with the main thread. But thread t will see this change because the main thread called t.start() and JLS guarantees that calling t.start() makes the change to x visible in t.run() so y is guaranteed to be assigned 1.

  • Thread join rule: All actions in a thread happen-before any other thread successfully returns from a join on that thread. Say thread A spawns a new thread B by calling threadA.start() then calls threadB.join(). Thread A will wait at join() call until thread B’s run method finishes. After join method returns, all subsequent actions in thread A will see all actions performed in thread B’s run method happened before them.
    A線程中調用B線程的 join() 操作,如果成功返回 則 B的所有操作對 join() 結果的返回一定是可見的,即在 A 線程能獲取到 B 對共享變量操作後的新值。
    【主線程能看見子線程對共享變量操作】

在這裏插入圖片描述
舉例

public class HappensBefore{

    static int value = 10;

    public static void main(String[] args) {

        Thread newThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //此處對共享變量進行修改
                value = 666;
            }
        });

        newThread.start();
        try {
            newThread.join();
            System.out.println(value);//子線程對共享變量的修改在子線程調用Join之後皆可見,此處value=666
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • Transitivity: If A happens-before B, and B happens-before C, then A happens-before C
    舉例
class VolatileTest{
 int index = 0;
 volatile boolean flag = false;
  
 public void write(){
   index  = 10;  // index 賦值 對 flag 是可見的
   flag = true;
 }public void read(){
  if(flag){
    System.out.println(index);
  }
}
}

在這裏插入圖片描述
根據“Single thread rule”可知,A線程中 index = 10 對 flag = true 可見;
根據“Volatile variable rule”可知,flag 使用 volatile 修飾的變量, A 線程 flag 寫 對 B 線程 flag 的讀可見;
綜合以上兩條規則:
index = 10 happen before 於 flag 的寫 ,
A 線程 flag 的寫 happen before 於 B 線程 flag 的讀,
根據傳遞性 , A 線程 對 index 的寫 happen before 於 B 線程 對 flag 的讀。所以 A 線程對 index 變量的寫操作對 B 線程 flag 變量的讀操作是可見的,即 B 線程讀取 index 的打印結果爲 10 。

參考:
https://www.jianshu.com/p/8446a398ca68
https://www.logicbig.com/tutorials/core-java-tutorial/java-multi-threading/happens-before.html
http://ifeve.com/easy-happens-before/
https://stackoverflow.com/questions/16248898/memory-consistency-happens-before-relationship-in-java
https://www.cnblogs.com/fanyi0922/p/11486580.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章