GithubHelp home page GithubHelp logo

jdk-sourcecode-analysis's People

Contributors

dependabot[bot] avatar fanofxiaofeng avatar seaswalker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jdk-sourcecode-analysis's Issues

内存屏障(对硬件)

来自: Is Parallel Programming Hard, And, If So, What Can You Do About It?

我们能信任的东西

即所有CPU(不同架构)均遵从的准则:

  1. 一个特定的CPU的所有访问操作都与该CPU上的程序顺序是一致的。
  2. 所有CPU对单个变量的访问都与存储这个变量的全局顺序有一些一致性
  3. 内存屏障成对操作。
  4. 互斥锁原语提供这样的手段。

第二条有意思,暂时不太准确理解其意思。几句耐人寻味的原文摘抄过来:

Because current commercially available computer systems provide cache coherence, if a group of CPUs all do concurrent non-atomic stores to a single variable, the series of values seen by all CPUs will be consistent with at least one global ordering.

Please note well that this section applies only when all CPUs’ accesses are to one single variable. In this single-variable case, cache coherence guarantees the global ordering, at least assuming that some of the more aggressive compiler optimizations are disabled via the Linux kernel’s ACCESS_ONCE() directive or C++11’s relaxed atomics [Bec11]. In contrast, if there are multiple variables, memory barriers are required for the CPUs to consistently agree on the order for current commercially available computer systems.

最后一句话,如果是对多个变量进行操作,那么需要memory barrier,换句话说,如果只有一个变量,无需memory barrier.

锁的内存屏障作用

锁的加锁和解锁操作必定以显性或隐性的方式存在memory barrier,这提供了内存顺序保证:第一个线程在临界区内的操作必定发生在第二个线程获得锁之前。Java的ReentrantLock的实现参考 #9

关于内存屏障不能做的假设

  1. 不能保证在内存屏障之前的内存访问将在内存屏障指令完成时完成;屏障仅仅用来在 CPU的访问队列中做一个标记,表示相应的访问不能被穿越。
  2. 不能保证在一个CPU中执行一个内存屏障将直接影响另外一个 CPU或者影响系统中其他硬件。间接效果是第二个CPU看到第一个CPU的访问是按顺序的。
  3. 不能保证一个CPU将看到第二个CPU 访问操作的正确顺序,即使第二个CPU使用一个内存屏障,除非第一个CPU也使用一个配对的内存屏障。
  4. 不能保证某些 CPU片外硬件不会重排对内存的访问。CPU缓存一致性机制将在CPU之间传播内存屏障的间接影响,但是不会按顺序执行这项操作。

种类

  1. 写(或存储) 内存屏障
  2. 数据依赖屏障
  3. 读内存屏障
  4. 通用内存屏障

屏障对

内存屏障必须成对使用才能推导出先后顺序。

当处理 CPU-CPU 交互时,几种类型的内存屏障总是应当配对。缺少适当的配对将总是会产生错误。一个写屏障应当总是与数据依赖屏障或者读屏障配对,虽然通用屏障也是可以的。类似的,一个读屏障或者数据依赖屏障总是应当与至少一个写屏障配对使用,虽然通用屏障也是可以的。

从CPU的角度看为什么需要内存屏障

MESI

CPU使用MESI协议(或其升级款)来保证其缓存的一致性。当CPU进行数据写入时,如果数据不在当前核心,那么会发送Read Invalidate消息,指示其它核心将此数据置为无效状态并发送给当前核心。

详解

直接参考书中附录B

概括

写内存屏障作用于store buffer,读屏障作用于无效队列,完整的屏障同时作用于两者

总结

  1. 每一个CPU总是按照编程顺序来感知内存访问
  2. 仅仅在操作不同地址时,CPU才对特定的存储操作进行重新排序
  3. 一个特定 CPU在内存屏障之前的所有装载操作(smp_rmb())将被所有随后的读内存屏障后面的操作之前被所有CPU所感知
  4. 所有在写内存屏障之前的写操作 (smp_wmb()) 都将比随后的写操作先感知
  5. 所有在内存屏障之前的内存访问 (装载和存储) (smp_mb())都将比随后的内存访问先感知

JMM和Linux内存屏障定义

JMM

根据: The JSR-133 Cookbook for Compiler Writers,Java内存模型定义了四种内存屏障:

  1. LoadLoad
  2. StoreStore
  3. LoadStore
  4. StoreLoad

其实就是load和store操作的全排列。

Linux

而Linux定义了三种内存屏障:

  1. 读屏障smp_rmb
  2. 写屏障smp_wmb
  3. 通用屏障smp_wb

关系

从字面上来看,smp_rmb对应LoadLoad,smp_wmb对应StoreStore,smp_mb对应LoadStore和StoreLoad,这样来说Java的定义更加细致一些,区分了Load和Store重排的两个不同方向。

读屏障

引用<Is Parallel Programming Hard, And, If So, What Can You Do About It?>中对它作用的说明:

The effect of this is that a read memory barrier orders only loads on the CPU that executes it, so that all loads preceding the read memory barrier will appear to have completed before any load following the read memory barrier.

重排

那么什么会导致相反地结果?我觉得可能有两种情况:

  1. 真正的乱序执行,即CPU重排两个load操作的顺序
  2. 隐式的重排:假设程序顺序上的第一条load操作cache miss,但第二条cache hit,那么可能第二条先于第一条完成

那么在这种情况下读屏障可以来保证顺序,怎么做到呢,猜测是禁止重排并且在第一条完成加载后再执行第二条。

Invalidate Queue

Is Parallel Programming Hard, And, If So, What Can You Do About It?书中给出了在存在Invalidate Queue的情况下读屏障的作用,代码如下:

// CPU 0执行,假设a处于share状态,b被CPU 0独占
void foo(void) {
    a = 1;
    smp_wmb();
    b = 1;
}

void bar(void) {
    while (b == 1) continue;
    assert (a == 1);
}

即使我们读到了b == 1,a还是有可能为0并导致断言失败。这里的原因并不是重排,而是执行bar函数的CPU没有将Invalidate Queue中的使无效消息应用到cache中,所以导致CPU读到了无效(过期)的a值。
所以需要一个读屏障来drain Invalidate Queue:

void bar(void) {
    while (b == 1) continue;
    smp_rmb();
    assert (a == 1);
}

所以我认为这里读屏障有两种实际的作用:禁止重排和drain Invalidate Queue

X86

  • X86平台没有Invalidate Queue,所有使无效消息都是实时处理的
  • X86保证了LoadLoad的先后顺序

这两点就保证了Java的LoadLoad和Linux的smp_rmb只是针对编译器的,而在硬件层面是一个nop

写屏障

通常CPU有store buffer这一结构(其实我也不知道是不是全都有,但X86和ARM是都存在)。假设CPU先后执行两个store操作:

a = 1;
b = 2;

和Load类似,我认为也有两种情形会导致b先于a被存储:

  1. 重排
  2. a没有被当前CPU在cache中独占,所以需要发送MESI消息来独占,在等待其它CPU响应期间将a扔到store buffer中。然后执行对b的存储,恰好b已经被当前CPU独占,那么就有可能实际上b先于a完成存储。

所以写屏障的作用就是:

  1. 禁止重排
  2. 在对b保存前如果发现存在写屏障,那么CPU要么将store buffer排干再处理b,要么把b也扔到store buffer,但是要保证store buffer FIFO

X86

x86平台会把所有写操作放到store buffer中,并保证写操作的先后顺序,所以在x86上StoreStore也是只针对编译器,对硬件是一个nop

LoadStore

x86保证了前面的load会先于store发生,至于原理不得而知,可能和store是异步的,而load是实时的有关。所以在x86上Java的LoadStore是一个只针对编译器的nop

StoreLoad

这是在x86上唯一没有被保证的,原因猜测是store是异步操作,load很容易跑到store前面。Java使用lock指令前缀来实现,而mfence也有同样的效果。
这一篇文章实锤了x86不保证StoreLoad: Memory Reordering Caught in the Act
mfence的作用是排干store buffer,在这之前封锁后续load操作。

但是是否x86保证了顺序,就意味着实际上完全没有重排发生了呢?可能不是这样的,重排可能还是有的,只不过是不可见的,比如对a和b的写入,两个变量恰好位于当前CPU的同一个缓存行,处于独占状态,其它CPU无法观察到两个变量写入的先后顺序。
参考: Does an x86 CPU reorder instructions?

总结

感觉内存屏障的细节真的很难理解,因为需要知道CPU内部的实现细节,而这一点则很难实现,比如对于ARM处理器就无法知道其store buffer是不是FIFO,有没有Invalidate Queue。

令人迷惑的volatile例子(一)

通常情况下关于volatile用法的正确例子是这样:

private volatile boolean flag = true;

@org.junit.Test
public void testVolatile() {
    new Thread(() -> {
        try {
            System.out.println("子线程启动");
            TimeUnit.SECONDS.sleep(3);
            flag = false;
            System.out.println("flag false");
        } catch (InterruptedException ignore) {
        }
    }).start();

    while (flag) {
    }
}

结果也确实如此,不会导致死循环,与之相反如果把volatile去掉:

private boolean flag = true;

的确会死循环,于是我们得出结论:必须使用volatile保证内存可见性。然而事实并不是如此,下面的例子只在while (flag)中加入一句无关痛痒的代码:

private boolean flag = true;

@org.junit.Test
public void testVolatile() {
    new Thread(() -> {
        try {
            System.out.println("子线程启动");
            TimeUnit.SECONDS.sleep(3);
            flag = false;
            System.out.println("flag false");
        } catch (InterruptedException ignore) {
        }
    }).start();

    while (flag) {
        System.out.print(1);
    }
}

你会发现,现在也不会死循环了。
其实,在X86平台上,即使不使用memory barrier处理器也会保证变量修改的全局可见。参考:
Does a memory barrier ensure that the cache coherence has been completed?

上面那个例子死循环的原因其实是JVM编译器优化,将代码实际上变成了(加上volatile或打印阻止了这种优化):

if (flag) {
    while (true) {}
}

参考:请问R大 有没有什么工具可以查看正在运行的类的c/汇编代码

那X86既然已经保证了全局可见,为什么我们还要使用volatile?Java毕竟是跨平台的语言,也许就会运行在不保证全局可见的处理器上,那时就会出错了。
所以如果你没在这种场景下用volatile,并不是你写的对,而是你恰好把代码跑在了X86上而已。

一种从值转为枚举的方法

代码示例:

public enum StatusEnum {

    CREATING(0),
    PUBLISHED(1),
    FINISHED(2);

    private final int status;

    StatusEnum(int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }

    private static final Map<Integer, StatusEnum> mapper = new HashMap<>(3);

    static {
        Arrays.stream(StatusEnum.values()).forEach(
            statusEnum -> mapper.put(statusEnum.getStatus(), statusEnum)
        );
    }
    
    /**
     * 将status值转为枚举.
     */
    public static StatusEnum fromStatus(final Integer status) {
        if (status == null) {
            return null;
        }

        return mapper.get(status);
    }

}

枚举的实现原理是:

  1. 枚举值实际上是public static final的字段
  2. 枚举不允许在枚举值之前存在任何代码
  3. fromStatus的调用导致StatusEnum类加载,而类加载后枚举字段已被初始化完毕,所以这样是行得通的

令人迷惑的volatile例子(三)

锁还是打印

#11 的基础上,把代码再改写为:

package test;
class MultiProcessorTask {

    private boolean flag = true;

    public void runMethod() {
        while (flag) {
            System.out.println(1);
        }
    }

    public void stopMethod() throws InterruptedException {
        System.out.println("准备睡眠1秒,然后置flag为false.");
        Thread.sleep(1000);
        System.out.println("change 'flag' field ...");
        flag = false;
    }
}
class ThreadA extends Thread {

    private MultiProcessorTask task;

    ThreadA(MultiProcessorTask task) {this.task = task;}

    @Override
    public void run() {
        task.runMethod();
    }
}
public class TestRun {
    public static void main(String[] args) throws InterruptedException {
        MultiProcessorTask task = new MultiProcessorTask();
        ThreadA a = new ThreadA(task);
        a.start();
        task.stopMethod();
        System.out.println("it's over");
    }
}

这样也能正常退出,众所周知,打印里面是有锁的,所以这里是打印还是锁阻止了JIT激进优化?不知道,对JIT优化的过程了解很少,我赌五毛是打印。

定时更新,定时读取

package test;

public class TestRun {

    private static int version = 0;

    private static class Writer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(50);
                    version += 1;
                    System.out.println("Writer更新为: " + version + ", 时间: " + System.currentTimeMillis());
                } catch (InterruptedException ignore) {
                }
            }
        }
    }

    private static class Reader implements Runnable {

        private final int index;
        private final long sleepTime;

        private Reader(int index, long sleepTime) {
            this.index = index;
            this.sleepTime = sleepTime * 10;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(sleepTime);
                    System.out.println("Read" + index + "读到: " + version + ", 时间: " + System.currentTimeMillis());
                } catch (InterruptedException ignore) {
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Reader(1, 3)).start();
        new Thread(new Reader(2, 4)).start();
        new Thread(new Writer()).start();
    }
}

一个写线程每隔50毫秒把version加一,两个读线程分别每隔30毫秒和40毫秒读一次version值,测试执行了147089次,触发了最高级别的C2/OSR/Level 4编译,在这种情况下仍可以保证Reader线程读到最新值,如下:

Writer更新为: 147087, 时间: 1593965931595
Read1读到: 147087, 时间: 1593965931603
Read2读到: 147087, 时间: 1593965931612
Read1读到: 147087, 时间: 1593965931635
Writer更新为: 147088, 时间: 1593965931646
Read2读到: 147088, 时间: 1593965931656
Read1读到: 147088, 时间: 1593965931665
Read1读到: 147088, 时间: 1593965931698
Writer更新为: 147089, 时间: 1593965931698
Read2读到: 147089, 时间: 1593965931698
Read1读到: 147089, 时间: 1593965931733
Read2读到: 147089, 时间: 1593965931739

编译级别:
image
image
至此彻底说明了,对于单个变量的并发读写在硬件层面上根本无需同步,给我们造成"不可见"现象的真正原因是JIT的激进优化。当然,JMM规范还是应当遵守的,毕竟不能保证所在的场景就一定不会被JIT做些手脚。这几个例子的用意是说清楚长久以来在🧠里的一些混乱。

ReentrantLock的内存可见性语义

加锁

以NonfairSync为例:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
     else
        acquire(1);
}

关键在于compareAndSetState方法:

/**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

注释里写了,此方法有相当于volatile读写的内存语义。所以这个内存语义又是什么?
参考Doug lea的: The JSR-133 Cookbook for Compiler Writers

  1. Issue a StoreStore barrier before each volatile store
  2. Issue a StoreLoad barrier after each volatile store.(Alternatively, if available, you can implement volatile store as an atomic instruction (for example XCHG on x86) and omit the barrier. This may be more efficient if atomic instructions are cheaper than StoreLoad barriers.)
  3. Issue LoadLoad and LoadStore barriers after each volatile load.

unsafe.compareAndSwapInt由Atomic::cmpxchg实现(Linux):

inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
  bool mp = os::is_MP();
  __asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
                        : "=a" (exchange_value)
                        : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                        : "cc", "memory");
  return exchange_value;
}

所以这在x86上就是加了lock前缀的cmpxhgg指令,而lock前缀在intel上便充当了读写memory barrier的作用,来自书中的摘抄L:

On the other hand, x86 CPUs have traditionally given no ordering guarantees for loads, so the smp_mb() and smp_rmb() primitives expand to lock;addl. This atomic instruction acts as a barrier to both loads and stores.

解锁

只看一行:

protected final boolean tryRelease(int releases) {
    // ...
    setState(c);
    // ...
}

/**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
protected final void setState(int newState) {
    state = newState;
}

从前面Doug lea的文章中可以看出,volatile写会导致在后面追加一个StoreLoad屏障,而此屏障在x86上:
image
So,也许又是一条lock前缀指令。

令人迷惑的volatile例子(二)

读了一次“错误”的并发控制引发的思考一文,觉得有些疑问。对于下面的代码:

class MultiProcessorTask {
    private boolean flag = true;

    public void runMethod() {
        while (flag) {
            synchronized (new Simple(1)){}
        }
    }
    public void stopMethod() {
        System.out.println("change 'flag' field ...");
        flag = false;
    }
}

原文的观点似乎倾向于synchronized带来的happens-before规则可以保证对flag的可见性,所以需要用JVM参数-XX:-EliminateLocks关闭锁消除优化就行了。
我的疑问在于:

  1. 内存屏障需要成对使用,对flag的写入并没有同步措施,以保证多个变量的内存操作顺序
  2. 上面代码实际上对单个变量的读写操作,我认为这种情况在硬件(尤其是x86)层面来说不需要任何内存屏障,缓存一致性协议即可保证变量对其它CPU核心的全局可见,这一点参考 #10Does a memory barrier ensure that the cache coherence has been completed?,高赞回答的核心逻辑就是: 对于单个变量,缓存一致性协议即可保证对CPU全局可见,内存屏障只是促使(加速)了这一点,所以在这种情况下加不加内存屏障只是一个快和慢的问题,不是可见与不可见的问题
  3. 我觉得上面的代码其实还是JIT如何优化的问题,关闭锁消除优化->while循环内含有锁->锁阻止了JIT生成死循环代码(猜测)

所以我把代码改写成了没有锁和volatile:

package test;
class MultiProcessorTask {

    private boolean flag = true;
    long sum = 0L;

    public void runMethod() {
        while (flag) {
            long a = System.currentTimeMillis() % 9;
            if (a == 1L) {
                sum += a;
            }
        }
        System.out.println("Result: " + sum);
    }

    public void stopMethod() throws InterruptedException {
        System.out.println("准备睡眠1秒,然后置flag为false.");
        Thread.sleep(1000);
        System.out.println("change 'flag' field ...");
        flag = false;
    }
}

class ThreadA extends Thread {

    private MultiProcessorTask task;

    ThreadA(MultiProcessorTask task) {this.task = task;}

    @Override
    public void run() {
        task.runMethod();
    }
}

public class TestRun {
    public static void main(String[] args) throws InterruptedException {
        MultiProcessorTask task = new MultiProcessorTask();
        ThreadA a = new ThreadA(task);
        a.start();
        task.stopMethod();
        System.out.println("it's over");
    }
}

直接运行,不会退出,加上JVM参数-Xint解释执行,会退出,这一步就说明了这个锅还是JIT的,下面通过jitwatch看一下JIT优化后的汇编代码。使用的JVM参数是:

-server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading  -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=live.log

没有volatile时:

  L0001: movabs $0x108289ce4,%r10
0x00000001117cc1f6: callq *%r10  ;*invokestatic currentTimeMillis
                                 ; - test.MultiProcessorTask::runMethod@7 (line 10)
0x00000001117cc1f9: mov %rax,%r10
0x00000001117cc1fc: mov %rax,%r11
0x00000001117cc1ff: sar $0x3f,%r11
0x00000001117cc203: movabs $0x1c71c71c71c71c72,%rax
0x00000001117cc20d: mov %r10,%r8
0x00000001117cc210: imul %r10
0x00000001117cc213: sub %r11,%rdx  ;*lrem
                                   ; - test.MultiProcessorTask::runMethod@13 (line 10)
0x00000001117cc216: mov %rdx,%r10
0x00000001117cc219: shl $0x3,%r10
0x00000001117cc21d: add %rdx,%r10
0x00000001117cc220: mov %r8,%r11
0x00000001117cc223: sub %r10,%r11
0x00000001117cc226: cmp $0x1,%r11
0x00000001117cc22a: jne L0002  ;*ifne ;如果余数不是1
                               ; - test.MultiProcessorTask::runMethod@18 (line 11)
0x00000001117cc22c: incq 0x10(%rbp)  ; OopMap{rbp=Oop off=144}
                                     ;*goto
                                     ; - test.MultiProcessorTask::runMethod@31 (line 14)
             L0002: test %eax,-0xa279236(%rip)  # 0x0000000107553000
                                                ;*goto
                                                ; - test.MultiProcessorTask::runMethod@31 (line 14)
                                                ;   {poll} *** SAFEPOINT POLL ***
0x00000001117cc236: jmp L0001
             L0003: xor %ebp,%ebp
0x00000001117cc23a: jmp L0000

可以看出,里面形成了一个死循环,不再判断flag的值,甚至也不把为1的余数加到sum中,每次循环只是取当前时间,然后取余。
而给flag加上volatile后的汇编代码为:

0x0000000119be7861: jmp L0002
             L0000: mov 0x10(%rbx),%r10  ;*getfield sum; 余数是1时跳到这里,取sum加总
                                         ; - test.MultiProcessorTask::runMethod@23 (line 12)
0x0000000119be7867: add $0x1,%r10
0x0000000119be786b: mov %r10,0x10(%rbx)  ;*putfield sum
                                         ; - test.MultiProcessorTask::runMethod@28 (line 12)
0x0000000119be786f: nop  ; OopMap{rbx=Oop off=80}
                         ;*goto
                         ; - test.MultiProcessorTask::runMethod@31 (line 14)
             L0001: test %eax,-0xc6fd876(%rip)  # 0x000000010d4ea000; 余数不是1跳到这里,取flag测试继续循环
                                                ;*aload_0
                                                ; - test.MultiProcessorTask::runMethod@0 (line 9)
                                                ;   {poll} *** SAFEPOINT POLL ***
             L0002: movzbl 0xc(%rbx),%r11d  ;*getfield flag
                                            ; - test.MultiProcessorTask::runMethod@1 (line 9)
0x0000000119be787b: test %r11d,%r11d; 测试flag是不是为false
0x0000000119be787e: je L0003  ;*ifeq; 是false,跳到L0003退出循环
                              ; - test.MultiProcessorTask::runMethod@4 (line 9)
0x0000000119be7880: movabs $0x10e289ce4,%r10
0x0000000119be788a: callq *%r10  ;*invokestatic currentTimeMillis
                                 ; - test.MultiProcessorTask::runMethod@7 (line 10)
0x0000000119be788d: mov %rax,%r11
0x0000000119be7890: movabs $0x1c71c71c71c71c72,%rax
0x0000000119be789a: imul %r11
0x0000000119be789d: mov %r11,%r10
0x0000000119be78a0: sar $0x3f,%r10
0x0000000119be78a4: sub %r10,%rdx  ;*lrem
                                   ; - test.MultiProcessorTask::runMethod@13 (line 10)
0x0000000119be78a7: mov %rdx,%r10
0x0000000119be78aa: shl $0x3,%r10
0x0000000119be78ae: add %rdx,%r10
0x0000000119be78b1: sub %r10,%r11
0x0000000119be78b4: cmp $0x1,%r11
0x0000000119be78b8: je L0000  ;*ifne; 如果余数是1,那么跳到L0000
                              ; - test.MultiProcessorTask::runMethod@18 (line 11)
0x0000000119be78ba: jmp L0001; 余数不是1,跳到L0001
            L0003: mov $0xffffff65,%esi

代码不同一目了然了。所以,在针对单个变量的前提下,不管是volatile还是加锁各种花式操作,所针对的都不是硬件层面上的可见性问题,而是如何阻止JIT激进优化的问题
两次汇编代码的优化级别都是:
image
其实,我的例子在不加volatile的情况下使用JVM参数-XX:-UseOnStackReplacement也能正常退出。

浮点数精度问题

精度问题出现的本质原因是浮点数无法精确的表示大多数十进制小数,以下面的计算为例:

System.out.println(81.6 * 10);
System.out.println(81.6 * 100);

其输出结果是:

816.0
8159.999999999999

数字81.6在写出来之后就不是精确表示了,随后的计算所以也随之不准确。下面的除法是准确的:

System.out.println(816 / 100D);

因为对于整数,816和100都是可以精确表示的,所以最后的结果便是对的。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.