JUC练习15——JMM(java内存模型)


链接:https://blog.csdn.net/weixin_44841312/article/details/120911816

一,什么是JMM?

它是一种java内存中数据存储的的协议规则:规定了内存是所有线程共有的,每个线程都有自己的工作内存,当进程需要使用数据时会去内存中读取数据放入自己的工作内存,然后对工作内存的数据进行操作,最终将操作后的数据写回到内存中。

二,八大操作

其实就是读写数据的原子操作,保证数据的读写不出现问题。

1,read操作:将数据从堆栈中读出来到一个缓冲区A

2,load操作:将缓冲区A的数据写入到工作内存

3,use操作:线程读取工作内存的数据进行计算

4,assign操作:线程在CPU中执行,将计算好的值存储到工作内存中

5,store操作:将工作内存大数据写入缓冲区B

6,write操作:将缓冲区B的数据写入原有的为止,对其进行赋值操作

7,lock操作:将堆栈中的变量加锁,标识为内存独占状态

8,unlock操作:堆栈中的变量解锁,其它线程就可以锁定该变量

三,volatile

 保证可见性(线程A修改的数据对线程B来说是可见的)
不保证原子性(例如a++实现的a+1操作其实是有多个步骤组成,他们是可分割的),synchronize保证代码操作原子性
保证有序性(代码的执行过程中,为了提高执行的效率会发生执行的重新排列)
1,可见性验证:
 private static int flag = 0;
    public static void main(String[] args)  {
        //主线程写一个死循环
        new Thread(()->
        {
            while (flag==0)
            {
                System.out.println("a");
            }
            flag = 1;
        }).start();
        //终止死循环
        flag=1;
        System.out.println(flag);
        //结果:无法终止死循环
        //解决,使用volatile:private static volatile int flag = 0;
        //说明volatile可以保证程序的可见性
    }

  2,验证volatile不保证原子性

private volatile static int sum = 0;
    private volatile static AtomicInteger sum1 = new AtomicInteger(0);
    public static void main(String[] args)  {
        //理论上的值为50000,但是由于volatile不能保证原子性,所以达不到50000
        for (int i=0;i<50;i++)
        {
            new Thread(()->
            {
                for (int j=0;j<1000;j++)
                {
                    //其实它在底层的步骤为:获取值,加1,写回值
                    sum++;
                    sum1.getAndIncrement();
                }
            }).start();
        }
        //保存计算的线程都执行完毕,留下的线程为main和gc
        while (Thread.activeCount()>2)
        {
            Thread.yield();
        }
        System.out.println(sum);
        System.out.println(sum1);
    }

3,保证有序性,防止指令重排

什么是指令重排:我们写的程序真正执行是并不一定会按照我们认为的顺序执行的,为了提高程序的执行效率,

编译器优化时会发生重排,执行指令并行时会发生重排,内存系统也会发生重排

编译器在执行指令重拍时,会考虑数据间的依赖性

int y =1 //1

int y=2  //2

x=x+5  //3

y=x*x  //4

我们期望执行的顺序:1234,最终执行的顺序可能是2134  1324,但不可能是4123,这由数据间的依赖性保证的

但是有时的指令重排是会导致数据出错的,此时如果加上了volatile关键字,就会在执行相应的命令组前后加上一组内存屏障,

在内存屏障中的指令就不会发生指令重排

 内存屏障在单例模式中用的最多,单例模式使用枚举的方式创建是最适合的

public enum Singleton
 {
    INSTANCE;//该枚举类对象的实例
    public void doSomething()
    {
        System.out.println("该枚举类实现的方法");
    }
 }

//调用的方式
public class Main {
    public static void main(String[] args) {
        Singleton.INSTANCE.doSomething();
    }
}