理解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运用的比较多的就是单例模式

posted @ 2021-12-21 11:45  Oh,mydream!  阅读(48)  评论(0编辑  收藏  举报