volatile特性及验证

作用

  1. 保证可见性;
  2. 防止指令重排;
  3. 但是不保证原子性;

可见性验证

可见性:在JMM(java memory model)java内存模型中,其他线程从主内存空间把值拷贝到自己的工作空间,线程修改之后的值会返回给主内存,主内存会通知其他线程,此为可见性。若线程修改之后的值未返回给主内存或主内存为通知其他线程即为不可见性
代码

public class VolatileTest {
    public static void main(String[] args) {
        Key key = new Key();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("进入操作数据线程");
                // 线程休眠1s
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 调用方法 改变flag的值
                key.turnFalse();
                System.out.println("值改变:  "+key.flag);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程结束");
            }
        });
        thread.start();
        // 若线程更改flag生效,主线程会结束
        while (key.flag){
        }
        System.out.println("main线程结束");
    }
}
class Key {
    public boolean flag = true;
    public void turnFalse(){
        this.flag = false;
    }
}

运行结果
在这里插入图片描述
代码
将Key的flag的关键字加上volatile下

    public volatile boolean flag = true

运行结果
在这里插入图片描述

结论

按照预想设计的逻辑,一旦线程更改的flag的值,while循环就会立即结束,可在未加volatile的程序中,while陷入了死循环,这代表while当时不可见flag的变化

提醒

volatile使得线程可以拷贝到正确的值,但static关键字可以让变量只存在一项实例,但实际上依然不能解决问题
static和volatile的区别

  1. volatile是告诉编译器,每次取这个变量的值都需要从主存中取,而不是用自己线程工作内存中的缓存.
  2. static 是说这个变量,在主存中所有此类的实例用的是同一份,各个线程创建时需要从主存同一个位置拷贝到自己工作内存中去(而不是拷贝此类不同实例中的这个变量的值),也就是说只能保证线程创建时,变量的值是相同来源的,运行时还是使用各自工作内存中的值,依然会有不同步的问题

    代码
class VolatileTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("进入操作数据线程");
                // 线程休眠1s
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 调用方法 改变flag的值
                Key.turnFalse();
                System.out.println("值改变:  "+Key.flag);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程结束");
            }
        });
        thread.start();
        // 若线程更改flag生效,主线程会结束
        while (Key.flag){
        }
        System.out.println("main线程结束");
    }
}
class Key {
    public static boolean flag = true;
    public static void turnFalse(){
        Key2.flag = false;
    }
}

运行结果
在这里插入图片描述
结论:主线程依然不可见flag的改变

防止指令重拍的验证

验证指令重拍现象

代码
class Test{
    static int a;
    static int b;
    static int y;
    static int x;
    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        while (true) {
            a = 0;
            b = 0;
            y = 0;
            x = 0;
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            });
            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            count++;
            if (x == 0 && y == 0) {
                System.out.println("程序发生了指令重排,第" + count + "次");
                break;
            }
        }
    }
}

定义了四个静态变量abxy,然后在一个死循环中两个线程分别对这四个变量进行赋值。
若程序不会发生指令重排,那么a = 1; 一定先于 x = b; 执行,b = 1; 一定先于 y = a; 执行,那么两个线程并发执行的排列组合就是C(4,2)=6 种组合的公式参考,如下

执行顺序执行后x的值执行后y的值
a=1,x=b,b=1,y=a01
a=1,b=1,x=b,y=a11
a=1,b=1,y=a,x=b11
b=1,y=a,a=1,x=b10
b=1,a=1,y=a,x=b11
b=1,a=1,x=b,y=a11

根据表格可知,x与y不可能同时为0,即循环不可能停止,但实际上程序停住了,如下图
在这里插入图片描述

结论

存在指令重排现象
使用volatile确实可以防止指令重拍现象,但不便验证,以此程序为例就是程序确实陷入了死循环

无法保证原子性

代码
class Test {
    public static void main(String[] args) throws Exception {
        Thread t1 = new Part();
        Thread t2 = new Part();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(Part.a);
    }
}
class Part extends Thread {
    volatile static int a = 0;
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
        }
    }
}
运行结果

不一定都是这么多,肯定在10000~20000之间(确实是有可能20000)
在这里插入图片描述

结论

若volatile可以保证原子性,那么结果肯定每次都是20000,但实际上大多数时候都是小于20000的,故而确定volatile不能保证原子性
注:若若想保证原子性还是需要synchronizedlock亦或是其它方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值