JMM、volatile

JMM

 

JMM规定:

按顺序执行read--load,且不可单独出现。
按顺序执行store-- write,且不可单独出现。
有assign操作后,变量改变后,需要同步到主内存中。
新变量必须诞生在主内存中,不可使用未load过的变量。
一个变量同一时刻只允许一个线程lock它。且一个lock就要一个unlock解开。
对变量lock操作,将会清空所有本地内存中此变量。
unlock一个变量前必须有store 和 write操作。

volatile

线程之间的可见性

线程A读取不到线程B刷新到主存的数据flag=fale,造成死循环

import java.util.concurrent.TimeUnit;

public class VolatileAccessibleTest {
    private static  boolean flag = true;

    public static void main(String[] args) {

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"即将进入while循环");
            while (flag){

            }
        },"A").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = false;
            System.out.println(Thread.currentThread().getName()+"设置flag=false,停止线程A");
        },"B").start();
    }
}

 

 可见性

public class VolatileAccessibleTest {
    private static volatile boolean flag = true;

    public static void main(String[] args) {

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"即将进入while循环");
            while (flag){

            }
        },"A").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = false;
            System.out.println(Thread.currentThread().getName()+"设置flag=false,停止线程A");
        },"B").start();
    }
}

 

 不保障线程的原子性

import java.util.concurrent.TimeUnit;

public class VolatileNoAtomicTest {
    private static volatile int num = 0;
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(num);
    }

    private static void add() {
        num++;
    }
}

 

 AtomicXXX原子类、synchronized、lock可以保障原子性
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class VolatileNoAtomicTest {
    private static AtomicInteger num = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(num);
    }

    private static void add() {
        num.getAndIncrement();
    }
}

 

禁止指令重排

 

 

double pi = 3.14; //A

double r = 1.0; //B

double area = pi * r * r; //C

指令重排:ABC正常、BAC正常、CAB不正常。

处理器在进行指令重排的时候,考虑数据之间的依赖性。

volitale避免指令重排,保证特定操作的执行顺序,可以保证某些变量的内存可见性

happens-before规则

happens-before表示的是前一个操作的结果对于后续操作是可见的,它是一种表达多个线程之间对于内存的可见性。所以我们可以认为在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须要存在happens-before关系。这两个操作可以是同一个线程,也可以是不同的线程。

内存屏障

  为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障
  • 在每个volatile写操作的后面插入一个StoreLoad屏障
  • 在每个volatile读操作的后面插入一个LoadLoad屏障
  • 在每个volatile读操作的后面插入一个LoadStore屏障

内存屏障

posted @ 2022-06-21 16:15  禁止摆烂  阅读(44)  评论(0)    收藏  举报