Java高并发程序-Chapter1 并行世界 (第三讲)Java 内存模型 JMM

 2019-12-10 16:15  阅读(984)
文章分类:Java Core

20191210001692\_1.png

1. 原子性 (Atomicity)

原子性是指一个操作是不可中断的。
即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰

i++是原子操作吗?

不是 (读取 运算 和 赋值 三个操作)

long 赋值 (8个字节 64位)

如果我们使用long型的话,对于32位系统来long型数据的读写不是原子性的(因为long有64位)

也就是说,如果两个线程同时对long进行写入的话(或者读取),对线程之间的结果是有干扰的

package com.john.learn.high.concurent.ch01.jmm.atomicity;

    public class MultiThreadLong {

        public static long t = 0;

        public static class ChangeT implements Runnable {

            public ChangeT(long to) {
                this.to = to;
            }

            public void run() {

                while (true) {
                    t = to;
                    Thread.yield();
                }
            }

            private long to;

        }

        public static class ReadT implements Runnable {

            public void run() {

                while (true) {

                    long temp = MultiThreadLong.t;

                    if (temp != 111l && temp != 333l && temp != -999l && temp != -444l) {

                        System.out.println(temp);
                    }

                    Thread.yield();
                }
            }

            private long to;

        }

        public static void main(String[] args) {

            /**
             * 32为虚机
             */
            new Thread(new ChangeT(111l)).start();
            new Thread(new ChangeT(333l)).start();
            new Thread(new ChangeT(-999l)).start();
            new Thread(new ChangeT(-444l)).start();

            new Thread(new ReadT()).start();
        }
    }

001000000000000000000000000000000000010
999=11111111111111111111111111111111111111111000001100
333=000000000000000000000000000000000000000000000010100110
44411111111111111111111111111111111111111111111001000100
+4294966852=000000000001111111111111111111111000
4294967185=111111111111111111111111111100000000000000000000000011

上面显示了这几个相关数字的补码形式,也就是在计算机内的真实存储内容不这个奇怪的4294966852,

其实是111或者333的前32位,与-444的后32位夹杂后42967185只99444的前32位与111夹杂后的数字

2. 可见性(Visibility)

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。

显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值

如果一个线程修改了某一个全局变量,那么其他线程未必可以马上知道这个改动。
图1.14展示了发生可见性问题的一种可能。
如果在CPU1和CPU2上各运行了一个线程,它们共享变量T,由于编译器优化或者硬件优化的缘故,在CPU上的线程将变量T进行了优化,将其缓存在Cache中或者寄存器里。
这种情况下,如果在CPU2上的某个线程修改了变量T的实际值,那么CPU1上的线程可能并无法意识到这个改动,依然会读取Cache中或者寄存器里的数据。
因此,就产生了可见性问题。外在表现为:变量T的值被修改,但是CPU1上的线程依然会读到一个旧值。

20191210001692\_2.png
可见性问题也是并行程序开发中需要重点关注的问题之一。

可见性问题是一个综合性问题。
除了上述提到的缓存优化或者硬件优化(有些内存读写可能不会立即触发,而会先进入一个硬件队列等待)会导致可见性问题外,
指令重排(这个问题将在下一节中更详细讨论)以及编辑器的优化,都有可能导致一个线程的修改不会立即被其他线程察觉。

  1. 有序性(Ordering)

在并发时,程序的执行可能就会出现乱序

假设线程A首先执行 writer方法,接着线程B执行 reader方法,如果发生指令重排,那
么线程B在代码第10行时,不一定能看到a已经被赋值为1了。如图1.15所示,显示了两个
线程的调用关系

20191210001692\_3.png20191210001692\_4.png

注意:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致

一条指令的执行是可以分为很多步骤的
– 取指 IF
– 译码和取寄存器操作数 ID
– 执行或者有效地址计算 EX
– 存储器访问 MEM

– 写回 WB

流水线的工作原理

20191210001692\_5.png

水线满载时,性能确实相当不错,但是一旦中断,所有的硬件设备都会进入个停顿期,再次满载又需要几个周期,因此,性能损失会比较大。所以,我们必须要想办法尽量不让流水线中断!

那么答案就来了,之所以需要做指令重排,就是为了尽量少的中断流水线

图1.17展示了A=B+C这个操作的执行过程。写在左边的指
就是汇编指令。LW表示load,其中LWR1,B,表示把B的值加载到Rl寄存器中。ADD指

就是加法,把R1、R2的值相加,并存放到R3中。SW表示 store, 存储,就是将R3寄存器

20191210001692\_6.png

20191210001692\_7.png

20191210001692\_8.png

20191210001692\_9.png

Happen-Before规则

程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C,那么A必然先于C
线程的start()方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Java高并发程序-Chapter1 并行世界 (第三讲)Java 内存模型 JMM

相关推荐