Volatile

Volatile,第1张

Volatile

Volatile是Java虚拟机提供轻量级的同步机制

  1.  保证可见性
  2. 不保证原子性
  3. 禁止指令重排

可见性

        可见性与Java的内存模型(JMM)有关,模型采用缓存与主存的方式对变量进行 *** 作,也就是说,每个线程都有自己的缓存空间,对变量的 *** 作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。

        volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写 *** 作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情:

  1. 将当前处理器缓存行的数据写回到系统内存
  2. 这个写回内存的 *** 作会使得在其他处理器缓存了该内存地址无效

意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。

eg://没有使用volatile,Thread1线程对主内存中的变价不知道,加了volatile可以保证可见性

package lin.jmm;

import java.util.concurrent.TimeUnit;

public class Test01 {
    private static int num=0;
    public static void main(String[] args) {
        new Thread(()->{
            while (num==0){

            }
        },"Thread1").start();

        //停一秒,使Thread1线程启动
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

 

eg://使用volatile

package lin.jmm;

import java.util.concurrent.TimeUnit;

public class Test01 {
    private volatile static int num=0;
    public static void main(String[] args) {
        new Thread(()->{
            while (num==0){

            }
        },"Thread1").start();

        //停一秒,使Thread1线程启动
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

原子性(不可分割)

        首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子 *** 作,如i = 1的赋值 *** 作,但是像j = i或者i++这样的 *** 作都不是原子 *** 作,因为他们都进行了多次原子 *** 作,比如先读取i的值,再将i的值赋值给j,两个原子 *** 作加起来就不是原子 *** 作了。

        线程A在执行任务的时候,不能被打扰,不能被分割,要么同时成功,要么同时失败。

        所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子 *** 作,就不会保证这个变量的原子性了。
 

package lin.volataile_Test;

//不保证原子性
public class Test02 {
    private volatile static int sum=0;
    public static void add(){
        sum++;
    }
    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {  //正常sum为2万
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" "+sum);
    }
}

加了synchronized 或 lock锁,就可以保证原子性

 如果不使用 lock 和 synchronized,怎么保证原子性?

        使用原子类,解决原子性问题

package lin.volataile_Test;

import java.util.concurrent.atomic.AtomicInteger;

//不保证原子性
public class Test02_AtomicInteger {
    private volatile static AtomicInteger sum = new AtomicInteger();
    public static void add(){
        //sum++; 因为是一个类,所以不能直接++ *** 作
        sum.getAndIncrement();  //AtomicInteger 的+1方法:  CAS

    }
    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {  //正常sum为2万
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" "+sum);
    }
}

 这些类的底层都是直接和 *** 作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

指令重排

什么是指令重排?

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

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

volatile可以避免指令重排问题:

        内存屏障。CPU指令。作用:

                1、保持特定的 *** 作的执行顺序!

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

       

 只要加了volatile就会在上面跟下面加一个内存屏障

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5160781.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-18
下一篇 2022-11-18

发表评论

登录后才能评论

评论列表(0条)

保存