volatile关键字

复制代码
volatile可以保证变量的可见性。
这里的变量包括类变量、实例变量,但不包括局部变量和方法参数,因为后者是线程私有的,不存在线程竞争问题

java内存模型(JMM)规定,所有变量都存储在主内存中,同时每个线程还有自己的工作内存。
线程对变量的所有操作(读取、赋值等),都必须在工作内存中进行,而不能直接读写主存中的数据
不同线程也无法访问对方工作内存中的变量,线程间变量值的传递需要通过主存来完成(引自周志明《深入理解Java虚拟机》)

volatile可以保证
(1)任何线程更新volitale修饰的变量后都会立即同步到主存中
(2)线程每次读取volitale修饰的变量,都必须到主存中取最新值
以上亮点保证了“可见性”,即任意线程对变量的修改能立即被其他线程所知
java 复制代码
package com.concurrent;

public class VolatileDemo {

    // 如果不加volatile,你会发现线程2已经结束很久了,线程1还在死循环。
    // 但是一旦加上volatile,线程2一执行,线程1会立刻跳出循环 
    // 这是因为volatile可以保证变量的可见性。
    // 这里的变量包括类变量、实例变量,但不包括局部变量和方法参数,因为后者是线程私有的,不存在线程竞争问题
    // java内存模型(JMM)规定,所有变量都存储在主内存中,同时每个线程还有自己的工作内存。
    // 线程对变量的所有操作(读取、赋值等),都必须在工作内存中进行,而不能直接读写主存中的数据,
    // 不同线程也无法访问对方工作内存中的变量,线程间变量值的传递需要通过主存来完成。
    // volatile可以保证
    // (1)任何线程更新volitale修饰的变量后都会立即同步到主存中
    // (2)线程每次读取volitale修饰的变量,都必须到主存中取最新值
    // 以上亮点保证了"可见性",即任意线程对变量的修改能立即被其他线程所知
    //
    //public static Integer money = 1000;
    public volatile static Integer money = 1000;

    public static void main(String[] args) throws InterruptedException {
        //线程1
        new Thread(() -> {
            while (money == 1000) {

            }
            System.out.println("存款已经不是" + money + "了");
        }).start();

        Thread.sleep(2000);

        //线程2
        new Thread(() -> {
            money = 900;
            System.out.println("存款现在是" + money);
        }).start();
    }
}

但是,volatile并不能保证原子性。下述代码如果能保证原子性的情况下应该返回200000,但实际执行后打印的结果远小于这个值。这是因为race++这个看似简单的操作实际上包含3个步骤:

(1)从主存获取值

(2)执行加1(iconst_1,iadd指令)

(3)写回主存

在线程执行iconst_1,i_add这些指令的时候,其他线程可能已经把race的值改变了,因此当前内存写回主存时就可能覆盖掉最新的值而把老值加1的结果写回去。(此处代码和解释均引自周志明《深入理解Java虚拟机》)

java 复制代码
package com.concurrent;

public class VolatileDemo2 {

    public static volatile int race = 0;

    public static void increase(){
        race++;
    }

    private static final int THREADS_COUNT = 20;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(()->{
                for (int j = 0; j < 10000; j++) {
                    increase();
                }

            });
            threads[i].start();
        }

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

        System.out.println(race);
    }
}

同时,volatile能防止指令重排。所谓指令重排,就是虚拟机在执行执行时,不考虑并发的影响,再保证结果不变的情况下,将部分指令重新排序的现象,也就是所谓的"线程内表现为串行"(With-Thread as-if-serial semantics).

一个典型的例子是懒汉是单例的双重检测锁模式。如果不加volatile修饰,由于new对象并非原子操作,就有可能出现指令重排的现象。new对象分为三个步骤(1)分配内存空间 (2)执行构造方法,初始化对象 (3)把对象指针执行这片空间。指令重排后原本(1)(2)(3)的顺序可能变成(1)(3)(2),这样有可能线程A执行完(1)(3),正要执行(2)的时候,线程B抢到执行权,判断lazyMan == null,为false,于是返回对象,但是此时的对象还并没有初始化,这时候去使用此对象显然会有问题。比如这里的使用就是打印,那就有可能出现先打印对象,然后再执行构造方法中的打印。但是,经过多次尝试,并没有出现1次这个问题,是概率太小还是理论有误?

java 复制代码
package com.concurrent;

public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    //private static LazyMan lazyMan = null;
    private static volatile LazyMan lazyMan = null;
    

    //双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan();
                }
            }
        }

        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    LazyMan lazyMan = LazyMan.getInstance();
                    System.out.println(lazyMan);
                }
            }).start();
        }
    }


}
相关推荐
啥都鼓捣的小yao几秒前
Python解决“特定数组的逆序拼接”问题
开发语言·python·算法
不爱学英文的码字机器14 分钟前
[操作系统] 进程间通信:匿名管道原理与操作
java·服务器·数据库
pen-ai16 分钟前
【R语言】二项分布,正态分布,极大似然估计实现
开发语言·r语言
memories19824 分钟前
Go语言不定长参数使用详解
开发语言·后端·golang
咩咩觉主32 分钟前
C# &Unity 唐老狮 No.10 模拟面试题
开发语言·unity·c#
Lecea_L32 分钟前
深入了解Java中的多线程:实现方法与常见坑
java
whysqwhw38 分钟前
Spring AOP:简化动态代理使用,支持声明式切面。
java
Lill_bin41 分钟前
冒泡排序:古老算法中的智慧启示
java·开发语言·数据结构·分布式·算法
Vacant Seat41 分钟前
二叉树-路径总和III
java·数据结构·算法·二叉树
JohnYan1 小时前
工作笔记 - Tomcat多实例部署研究和实现
java·后端·操作系统