JAVA:【基础五】多线程Volatile关键字

目录

一、volidate关键字定义解释

二、概念1的解释:保证【可见性】

三、概念2的理解:保证【有序性】

四、volatile使用的场景

volatile能使用的场景不多,使用原则是

使用场景一:状态标记

使用场景二:声明单例

场景二解释:声明单例为什么需要使用volatile、以及双重检查

五、哪些操作是原子性的

六、synchronized/lock如何实现原子性、有序性、可见性

        原子性

        有序性

        可见性

参考文章

一、volidate关键字定义解释

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

        强制将对缓存的修改操作(即写操作)立即写入主存如果是写操作导致其他线程中对应的缓存无效,让其他线程只能从主存中拿刚刚更新的。

2)禁止指令重排序。

3)volatile只能保证【可见性】、【有序性】,并不能保证【原子性】。

4)synchronized和Lock能保证【可见性】、【有序性】、【原子性】

二、概念1的解释:保证【可见性】

  1. 主存和线程的工作内存:
    1. Java内存模型规定所有的变量都是存在主存当中
    2. 每个线程都有自己的工作内存【可以理解为每个线程都会创建一个独立的工作栈
  2. 线程在工作时,会将主存中的值读取到自己的工作内存中,然后自己的工作内存中对该值进行改变,然后再刷新到主存中。这样就存在这么一种情况:
    1. 线程1从主存中读取i=10,并加载到自己的工作内存
    2. 线程1执行i=i+10,即将i的值改变为20
    3. 线程2从主存中读取i=10【因为此时线程1并未将i=20刷新到主存当中】
    4. 线程2执行i=i-5,将i=的值改变为5
    5. 然后线程1,线程2分别将值刷新到主存中
    6. 主线程从主存中再读取i值的时候,并不能得到10+10-5=15的正确结果
  3. 固需要volatile关键字来使改变后的值立即刷新到主存中,即遵循多线程的【可见性】

三、概念2的理解:保证【有序性】

  1. 了解java执行时的顺序
    1. 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
      int a = 10;    //A
      int r = 2;     //B
      a = a + 3;     //C
      r = a*a;       //D

      在执行的时候,可能存在B,A,C,D的顺序。

  2. 现有如下代码:

    //线程1:
    context = loadContext();   //语句1
    inited = true;             //语句2
     
    //线程2:
    while(!inited ){
      sleep()
    }
    doSomethingwithconfig(context);

    上述代码期望的执行逻辑是:

    1. 线程1通过loadContext()方法对context进行赋值

    2. 然后将inited赋值为true

    3. 因为线程2是一个while循环,当inited被改变成true时,就跳出了循环,执行doSomethiingwithconfig(context)【此时context已经在线程1中被赋值】

    4. 但是因为javav的指令重排特性,可能存在如下执行顺序

      1. 先执行线程1的inited=true;

      2. 此时线程2监听到initedtrue,开始执行doSomethiingwithconfig(context),但是由于cocntext还未被赋值,所以会抛出异常

      3. 线程1再通过loadContext()方法对context进行赋值

    5. 可以使用volatile修饰 inited=true来解决这个问题,添加了volatile以后的执行逻辑

      1. 因为volatile修饰了inited=true,固:会强制保证inited=true之前的代码已经执行完毕,且结果对inited=true及以后的代码执行是可见的

      2. 综上,执行的流程会与期望一致

四、volatile使用的场景

volatile能使用的场景不多,使用原则是

                1)对volatile变量的写操作不依赖于当前值

                2)该volatile变量没有包含在具有其他变量的不变式中

使用场景一:状态标记

volatile boolean inited = false;   // inited 是 volatile 变量,是状态标志量
//线程1:
context = loadContext();  
inited = true;            
 
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

使用场景二:声明单例

class Singleton{
    private volatile static Singleton instance = null;  //  instance 是 volatile 变量
     
    private Singleton() {
         
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

场景二解释:声明单例为什么需要使用volatile、以及双重检查

        参考文章:https://blog.csdn.net/qq_36769100/article/details/122966636https://blog.csdn.net/qq_36769100/article/details/122966636

五、哪些操作是原子性的

只有读取和赋值【而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作】才是原子操作

六、synchronized/lock如何实现原子性、有序性、可见性

        原子性

synchronized和Lock通过保证任一时刻只有一个线程执行含有共享变量的代码块(对于没有 synchronized和Lock修饰的非同步方法、非同步代码块,不会阻塞的,它们与 synchronized和Lock无关),那么自然就不存在原子性问题了,从而保证了原子性。

        有序性

synchronized和Lock通过保证同一时刻只有一个线程获取锁然后执行同步代码(保证原子性),并且在释放锁之前会将对变量的修改刷新到主存当中(保证可见性),因此可以保证可见性。

        可见性

synchronized和Lock保证每个时刻是有一个线程执行同步代码(保证原子性),其原子内部顺序执行,保证有序性,原子外部没有互斥资源,不需要保证有序性,所有保证了有序性。

参考文章

【Java并发】面试官:谈一谈你对volidate关键字的理解? - 灰信网(软件开发博客聚合)【Java并发】面试官:谈一谈你对volidate关键字的理解?,灰信网,软件开发博客聚合,程序员专属的优秀博客文章阅读平台。https://www.freesion.com/article/98331106579/#Javasynchronizedlock_157

你可能感兴趣的:(JAVA,java,开发语言,后端)