JUC并发编程(基础入门七)——JMM、volatile、禁止指令重排

1 JMM

1对Volatile 的理解

volatile 是 Java 虚拟机提供 轻量级的同步机制,具有1、保证可见性 2、不保证原子性 3、禁止指令重排

volatile 能使得一个非原子操作变成原子操作吗?
关键字volatile的主要作用是使变量在多个线程间可见但无法保证原子性,对于多个线程访问同一个实例变量需要加锁进行同步

虽然volatile只能保证可见性不能保证原子性,但用volatile修饰long和double可以保证其操作原子性。

2什么是JMM

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁;

线程中分为 工作内存、主内存

8种操作:
Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

在这里插入图片描述
在这里插入图片描述
JMM对这8种操作给了相应的规定:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
在这里插入图片描述

import java.util.concurrent.TimeUnit;

public class Test {
    
    

    // 如果不加volatile 程序会死循环
    // 加了volatile是可以保证可见性的
//    private volatile static Integer number = 0;
    private  static Integer number = 0;

    public static void main(String[] args) {
    
    
        //main线程

        //子线程1
        new Thread(()->{
    
    
            while (number==0){
    
    
            }
        }).start();

        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        number=1;
        System.out.println(number);
    }
}

遇到问题:程序不知道主存中的值已经被修改过了

2 volatile

1保证可见性

加了volatile是可以保证可见性的

import java.util.concurrent.TimeUnit;

public class Test {
    
    

    // 如果不加volatile 程序会死循环
    // 加了volatile是可以保证可见性的
    private volatile static Integer number = 0;

    public static void main(String[] args) {
    
    
        //main线程

        //子线程1
        new Thread(()->{
    
    
            while (number==0){
    
    
            }
        }).start();

        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        number=1;
        System.out.println(number);
    }
}

2不保证原子性

原子性:不可分割;线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

/**
 * 不保证原子性
 * number <=2w
 *
 */
public class Test {
    
    

    private static  int number = 0;

    public static void add(){
    
    
        number++;
        //++ 不是一个原子性操作,是两个~3个操作
    }

    public static void main(String[] args) {
    
    
        //理论上number  === 20000
        for (int i = 1; i <= 20; i++) {
    
    
            new Thread(()->{
    
    
                for (int j = 1; j <= 1000 ; j++) {
    
    
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
    
    
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }
}

main,num=19885

在这里插入图片描述
如果不加lock和synchronized ,怎么样保证原子性?
在这里插入图片描述
在这里插入图片描述

3使用原子类解决原子性的问题

在这里插入图片描述

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 不保证原子性
 * number <=2w
 *
 */
public class Test {
    
    

    private static  volatile AtomicInteger number = new AtomicInteger();

    public static void add(){
    
    
//        number++;
        //++ 不是一个原子性操作,是两个~3个操作

        number.getAndIncrement(); //atomicInteger +1方法 cas
    }

    public static void main(String[] args) {
    
    
        //理论上number  === 20000
        for (int i = 1; i <= 20; i++) {
    
    
            new Thread(()->{
    
    
                for (int j = 1; j <= 1000 ; j++) {
    
    
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
    
    
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }
}

main,num=20000

4禁止指令重排

什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行

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

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4

//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的

在这里插入图片描述
可能在线程A中会出现,先执行b=1,然后再执行x=a;
在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.

volatile可以避免指令重排: :volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

内存屏障:CPU指令。作用:1、保证特定的操作的执行顺序;2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

在这里插入图片描述
总结
volatile可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生

5volatile 修饰符的有过什么实践?

面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式

3synchronized 和 volatile 的区别是什么?

synchronized :表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程
volatile保证多线程环境下变量的可见性;禁止指令重排序。

1 volatile 是变量修饰符synchronized 可以修饰类、方法、变量
2 volatile 仅能实现变量的修改可见性不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
3 volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
4 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

猜你喜欢

转载自blog.csdn.net/zs18753479279/article/details/114205188