shuvigoss / notes

0 stars 0 forks source link

【java多线程二】指令重排 #5

Open shuvigoss opened 4 years ago

shuvigoss commented 4 years ago

指令重排

class ReorderExample {
    int a = 0;
    boolean flag = false;

    public void writer() {
        a = 1;                   //1
        flag = true;             //2
    }

    public void reader() {
        if (flag) {                //3
            int i =  a * a;        //4
            //TODO
        }
    }
}

以上代码如果在一个线程内执行,不会出现问题,但是CPU会在他认为合理的情况下对两个不相关的指令进行重排,如果线程A执行writer,线程B执行reader,会有如下顺序:

线程A对1和2进行了重排 image

线程B对3和4进行了重排 image

因此,由于重排,导致不一样的结果,为了解决因重排出现的不一致问题,JMM规定了如下规则来避免发生不一致情况。

http://ifeve.com/jmm-cookbook-reorderings/ http://gee.cs.oswego.edu/dl/jmm/cookbook.html https://tech.meituan.com/2014/09/23/java-memory-reordering.html

image

shuvigoss commented 4 years ago
shuvigoss commented 4 years ago
    int a = 0;
    int b = 0;
    /**
     * <pre>
     * 第一个操作
     * Normal Load
     * Normal Store
     *
     * 第二个操作
     * Normal Load
     * Normal Store
     *
     * 结论:可以重排
     * </pre>
     */
    public void NormalLoad_NormalStore$$NormalLoad_NormalStore() {
        a = 1;
        b = 1;
    }
    volatile int c = 0;
    int d = 0;

    /**
     * <pre>
     * 第一个操作
     * Normal Load
     * Normal Store
     *
     * 第二个操作
     * Volatile load
     * MonitorEnter
     *
     * 结论:可以重排
     * </pre>
     */
    public void NormalLoad_NormalStore$$VolatileLoad_MonitorEnter() {
        d = 0;
        System.out.println(c);
    }
    int e = 0;
    volatile int f = 0;
    /**
     * <pre>
     * 第一个操作
     * Normal Load
     * Normal Store
     *
     * 第二个操作
     * Volatile store
     * MonitorExit
     *
     * 结论:不可以重排
     * </pre>
     */
    public void NormalLoad_NormalStore$$VolatileStore_MonitorExit() {
        e = 1;
        f = 1;
    }
shuvigoss commented 4 years ago
    volatile int g = 0;
    int h = 0;

    /**
     * <pre>
     * 第一个操作
     * Volatile load
     * MonitorEnter
     *
     * 第二个操作
     * Normal Load
     * Normal Store
     *
     * 结论:不可以重排
     * </pre>
     */
    public void VolatileLoad_MonitorEnter$$NormalLoad_NormalStore() {
        int a = g;
        h = 1;
    }
    volatile int i = 0;
    volatile int j = 0;

    /**
     * <pre>
     * 第一个操作
     * Volatile load
     * MonitorEnter
     *
     * 第二个操作
     * Normal Load
     * Normal Store
     *
     * 结论:不可以重排
     * </pre>
     */
    public void VolatileLoad_MonitorEnter$$VolatileLoad_MonitorEnter() {
        int a = i;
        int b = j;
    }
    volatile int k = 0;
    volatile int l = 0;

    /**
     * <pre>
     * 第一个操作
     * Volatile load
     * MonitorEnter
     *
     * 第二个操作
     * Volatile store
     * MonitorExit
     *
     * 结论:不可以重排
     * </pre>
     */
    public void VolatileLoad_MonitorEnter$$VolatileStore_MonitorExit() {
        int a = k;
        int l = 1;
    }
shuvigoss commented 4 years ago
    volatile int m = 0;
    int n = 0;

    /**
     * <pre>
     * 第一个操作
     * Volatile store
     * MonitorExit
     *
     * 第二个操作
     * Normal Load
     * Normal Store
     *
     * 结论:可以重排
     * </pre>
     */
    public void VolatileStore_MonitorExit$$NormalLoad_NormalStore() {
        m = 1;
        n = 1;
    }
    volatile int o = 0;
    volatile int p = 0;

    /**
     * <pre>
     * 第一个操作
     * Volatile store
     * MonitorExit
     *
     * 第二个操作
     * Volatile load
     * MonitorEnter
     *
     * 结论:不可以重排
     * </pre>
     */
    public void VolatileStore_MonitorExit$$VolatileLoad_MonitorEnter() {
        o = 1;
        int a = p;
    }
    volatile int r = 0;
    volatile int s = 0;

    /**
     * <pre>
     * 第一个操作
     * Volatile store
     * MonitorExit
     *
     * 第二个操作
     * Volatile store
     * MonitorExit
     *
     * 结论:不可以重排
     * </pre>
     */
    public void VolatileStore_MonitorExit$$VolatileStore_MonitorExit() {
        r = 1;
        s = 1;
    }
bingoohuang commented 4 years ago

配图

image

bingoohuang commented 4 years ago

为什么要指令重排序?

我们知道java在运行的时候有两个地方可能用到重排序,一个是编译器编译的的时候,一个是处理器运行的时候。 那么我们就应该问问为啥要用指令重排序呢?

生活类比 我们从生活中举个例子,假设你有一箱红纸,现在要你剪成小红花贴在窗上。你有两种极端的选择:拿出来一个,把这个剪好,再贴上去......一个一个依次进行;另一种方式是先全部拿出来,然后全部剪好,最后全部贴上去。 那种效率更高?很明显是后者,因为前者你就需要不停地在箱子,剪刀和胶水之间切换,这个切换过程不仅浪费时间,还耗费精力。但是后者一直做一个工作也很无聊,还会导致半天了窗上一朵花都没有,会给你带来失落感,所以比较合适的做法就是拿出来一叠,把这一叠剪好,贴上去。这样既不无聊,也减少了切换次数,提高了工作效率。 再想想,如果有三个人,一个负责拿,一个负责剪,一个负责贴,就更快了。

作者:Mageek Chiu 链接:https://juejin.im/post/5b0b56f6f265da0dd6488083 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

bingoohuang commented 4 years ago

image 图来自Java Memory Model - Reordering Problem

A field cannot be final and volatile at the same time, doing so is a compile time error.

definitions in concurrency theory:

image

volatile[About] as an example

A write to a volatile field happens-before every subsequent read of that field.

Let's take a look at the example:

// Definitions
int a = 1;
int b = 2;
volatile boolean myVolatile = false;

// Thread A. Program order
{
    a = 5;
    b = 6;
    myVolatile = true; // <-- write
}

//Thread B. Program order
{
    Thread.sleep(1000); //just to show that writing into `myVolatile` was executed before

    System.out.println(myVolatile); // <-- read
    System.out.println(a);  //prints 5, not 1
    System.out.println(b);  //prints 6, not 2
}

Visibility - When Thread A changes/writes a volatile variable it also pushes all previous changes into RAM - Main Memory as a result all not volatile variable will be up to date and visible for another threads

Ordering: