JUC_Volatile简述

概述

Volatile关键字是JVM提供的轻量级同步机制,其特性为:

1.保证可见性

2.不保证原子性

3.禁止指令重排

保证可见性

示例代码

public class Demo01 {
    private static volatile int num = 0;

    public static void main(String[] args) {
        new Thread(()->{
            while (num==0){
            };
        }).start();

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

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

运行效果

主线程执行完 num = 1 语句后就马上结束了,显然此时子线程知晓了num == 1,跳出了while循环,子线程结束,主线程也随之结束

换言之,主线程中对num的修改被子线程知晓了,此为可见性

不保证原子性 

原子性:一个操作或者多个操作 要么全部执行 并且执行的过程不会被任何因素打断 要么就都不执行
若要保证原子性:
1.上锁 即synchronized或lock
2.使用原子类 AtomicxXXX

示例代码

public class Demo02 {
    //++本身即是非原子性操作
    private volatile static int num;
    private volatile static AtomicInteger AtomicNum = new AtomicInteger();

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

    private static void AtomicAdd(){
        AtomicNum.addAndGet(1);//底层使用CAS实现
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 10000; j++) {
                    add();
                }
            }).start();
        }

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 10000; j++) {
                    AtomicAdd();
                }
            }).start();
        }

        //JVM中一直开启的线程 main GC
        //所以Thread.activeCount()>2时 可知上述线程尚未执行完
        //此处作用是让main线程让出CPU直到上述线程执行完 否则会在上述线程未执行完的情况下就输出执行输出语句
        while (Thread.activeCount()>2){
            Thread.yield();
        }

        System.out.println("num : " + num);
        System.out.println("AtomicNum : " + AtomicNum);
    }
}

运行效果

可以看到,num的值明显不符合预期,这是因为num++并不是原子性操作,并发情况下会出现多个线程同时执行num++的情况;假定一时刻num == 100,此时线程A与线程B同时执行num++,A与B均认为num == 100,所以最终二者向主内存提交的结果均为num == 101,导致最终结果缩水

禁止指令重排

指令重排:指令重排序是指编译器或CPU为了优化程序的执行效率 而对不存在数据依赖的指令进行重新排序的一种机制 并发时指令重排可能会导致预期与结果不一致

关于可见性问题的说明:

一.单线程情况下,无可见性问题

public class Demo03 {
    public static void main(String[] args) {
        int i = 1;    //1
        int j = 1;    //2
        int k = i + j;//3

        System.out.println(k);
    }
}

上述代码中,1与2不存在数据依赖性,所以可能会被指令重排,调换执行顺序;而3数据依赖1与2,所以不可能被指令重排,先于1、2执行

二.多线程情况下,可能引发可见性问题

public class Demo03 {
    static int num = 0;
    static boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            if (flag) {                        //1
                num = num + num;               //2       
                System.out.println(num);       
            }
        }).start();

        new Thread(() -> {
            num = 1;                           //3
            flag = true;                       //4
        }).start();
    }
}

若在单线程情况下,1数据依赖4,2数据依赖3,所以3、4必然先于1、2执行,可能的执行顺序为3412或4312;但在多线程情况下,3、4可能会被指令重排,若此时的执行顺序为4123,则会导致输出的结果为0,产生预期与结果不一致的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值