理解JMM

发布时间:2022-06-27 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了理解JMM脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

理解JMM

volatile 是java虚拟机提供轻量级的同步机制,是一个java关键字

1.保证可见性

2.不保证原子性

3.禁止指令重排

JMM java 内存模型,是一种约定,不是真实存在的。

关于JMM的一些约定

1.线程解锁前,必须把共享变量刷回主存

2.线程加锁前,必须读取主存的最新值到工作内存中

3.加锁和解锁必须是同一把锁

JMM规定,内存主要花粉为主内存工作内存两种。主内存对应的是java堆中的对象实例部分,工作内存对应的是栈中的部分区域。每条线程拥有各自的工作内存,是主内存的一份拷贝。

8种操作(均为原子性操作)

lock: (锁定)作用于主内存的变量,把一个变量标识为线程独占状态

unlock:(解锁)作用于主内存的变量,把一个线程独占状态的变量释放,可以被其他线程占用

read:(读取)作用于主内存变量,将主内存变量读取到缓存中

load:(载入)作用于工作内存变量,将缓存中的变量写入到工作内存中

use:(使用)作用于工作内存变量,将工作内存中的变量传输给执行引擎

assign:(赋值)作用于工作内存变量,将执行引擎中的变量传输给工作内存

Store:(存储)作用于工作内存变量,将工作内存变量读入缓冲区

wrITe:(写入)作用于主内存变量,将工作内存变量读入主内存

原则:

必须成对出现

一、保证可见性

package com.example.juc;

import java.util.concurrent.TimeUnit;

public class testVolatile {
    PRivate static volatile int num = 0; //不加volatile程序会死循环,加了volatile保证了可见性

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            while (num == 0) {

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);
        num = 1;
        System.out.println(num);

    }
}

二、不保证原子性

demo

package com.example.juc;

public class TestVolatile2 {
    private volatile int num = 0;

    public static void main(String[] args) {
        TestVolatile2 testVolatile2 = new TestVolatile2();
        testVolatile2.test();

    }

    public void test() {

        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }

            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(num);
    }

    public void add() {
        num++;
    }
}

16579

add方法设置为synchronized

package com.example.juc;

public class TestVolatile2 {
    private volatile int num = 0;

    public static void main(String[] args) {
        TestVolatile2 testVolatile2 = new TestVolatile2();
        testVolatile2.test();

    }

    public void test() {

        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }

            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(num);
    }

    public synchronized void add() {
        num++;
    }
}
20000

不使用synchronied 和lock如何保证原子性?

使用JUC下的Atomic包,该包下面使用了Unsafe类,在CAS里面详细讲

package com.example.juc;

import java.util.concurrent.atomic.AtomicInteger;

public class TestVolatile3 {
    private static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) {
        TestVolatile3 testVolatile2 = new TestVolatile3();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    testVolatile2.add();
                }

            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(num);
    }

    public void add() {
        num.getAndIncrement();
    }
}

三、禁止指令重排

什么是指令重排?你写的程序,计算机并不是按照你写的那样去执行的,会进行优化重排

代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行

处理器在进行指令重排的时候会考虑指令的依赖性!

1.不影响结果的重排

int a =0; // 1
int b = 0; // 2
a = a + 3; // 3
b = b + 5; // 4

我们认为计算器执行的是1,2,3,4

实际执行可能是1324 2134 2143

但是不可能是4213

2.影响结果的重排

x,y,a,b初始值均为0

线程A 线程B
x = a y = b
b = 2 a=1

result: x=0,b=2,y=0,a=1

指令重排后,结果会变化

线程A 线程B
b = 2 a=1
x = a y = b

result: x=1,b=2,y=2,a=1

volatile关键字可以避免指令重排!

利用CPU指令中的内存屏障

内存屏障的作用:

1.保证特定操作的执行顺序

2.可以保证某些变量的内存可见性(利用这些性质volatile实现了可见性)

volatile运用的比较多的就是单例模式

脚本宝典总结

以上是脚本宝典为你收集整理的理解JMM全部内容,希望文章能够帮你解决理解JMM所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。