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

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