证明volatile 是线程可见的

1.介绍

Volatile关键字的作用主要有如下两个:
1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
2. 顺序一致性:禁止指令重排序。

本文主要介绍 java中 volatile 关键字是线程可见的,以前了解的是一个线程对 volatile 修饰的遍历修改之后,另一个线程再次获取这个变量的值的时候,就是修改之后的值了。如果变量没有 volatile 修饰,另一个线程就不会感知到便获。        其实,这就需要了解JMM内存模型。

下面介绍我对 volatile 关键字的部分理解,在了解JMM 内存模型之后,明白线程是在哪里取值的,才能进一步理解 volatile 的意思。我认为,一个变量 Boolean  flag 如果没有被 volatile 修饰,线程加载这个变量之后,会放入线程的内存中,在使用的时候,一直从线程内存中取值,对于主内存flag变量的变化,线程是不会感知的,也就是说,如果其他线程对flag进行操作,改变了他的值,那么这个线程也不会感知;但是也仅限于,线程内存flag值不会消失的情况,如果这个变量在线程内存中消失了,他再去主内存加载的时候,就是新的值,如果变量flag在线程内存中长时间不用,(这个时间也很短,大约几百毫秒的样子--我猜的,反正很短时间,因为线程内存也很小,对于不常用的变量就会丢弃,常用的就在里面存着方便下次使用)也可能会消失,线程再次取值的时候,只能去主内存取值,如果在取值之前,被另一个线程改变了,他取到的值就是改变后的值。

代码证明线程可见性

package com.example.demovalid;

public class test1 {

// 变量
    Boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        test1 test1 = new test1();

// 线程1 取变量的值
        Thread t1 = new Thread(() -> {

            System.out.println("这是--"+ Thread.currentThread() + "---" + test1.flag);


            // 这里死循环,是为了让线程1不断的取值,这里是取的线程内存的值,为了不让变量在线程内存中消失,不会去加载主内存的值
            while (true){

                if (!test1.flag){
                    System.out.println("这是11--"+ Thread.currentThread() + "---" + test1.flag);
                    break;
                }

            }
            System.out.println("这是222--"+ Thread.currentThread() + "---" + test1.flag);

        }, "t1");

// 线程2取变量的值,并对其修改
        Thread t2 = new Thread(() -> {
            try {
// 等1秒,为了让线程1启动起来
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

// 打印值
            System.out.println("这是--"+ Thread.currentThread() + "---" + test1.flag);
            System.out.println("t2" + System.currentTimeMillis());
// 对变量修改
            test1.flag= false;

        }, "t2");

// 线程1和2设置为守护线程,当主线程结束的时候,他们也结束
        t1.setDaemon(true);
        t2.setDaemon(true);

// 启动线程
        t1.start();
        t2.start();

// 主线程等待10秒,为了看线程1和2打印变量的值
        Thread.sleep(10000L);

        System.out.println("结束");

    }
}

上边是没有修饰的变量执行后打印的结果

这是--Thread[t1,5,main]---true
这是--Thread[t2,5,main]---true
t21671368338074
结束

从上面结果可以看出线程1没有感知到线程2对变量的修改,一直在循环中。

修饰之后的代码

package com.example.demovalid;

public class test1 {

// 修饰的变量
    volatile Boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        test1 test1 = new test1();

// 线程1 取变量的值
        Thread t1 = new Thread(() -> {

            System.out.println("这是--"+ Thread.currentThread() + "---" + test1.flag);


            // 这里死循环,是为了让线程1不断的取值,这里是取的线程内存的值,为了不让变量在线程内存中消失,不会去加载主内存的值
            while (true){

                if (!test1.flag){
                    System.out.println("这是11--"+ Thread.currentThread() + "---" + test1.flag);
                    break;
                }

            }
            System.out.println("这是222--"+ Thread.currentThread() + "---" + test1.flag);

        }, "t1");

// 线程2取变量的值,并对其修改
        Thread t2 = new Thread(() -> {
            try {
// 等1秒,为了让线程1启动起来
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

// 打印值
            System.out.println("这是--"+ Thread.currentThread() + "---" + test1.flag);
            System.out.println("t2" + System.currentTimeMillis());
// 对变量修改
            test1.flag= false;

        }, "t2");

// 线程1和2设置为守护线程,当主线程结束的时候,他们也结束
        t1.setDaemon(true);
        t2.setDaemon(true);

// 启动线程
        t1.start();
        t2.start();

// 主线程等待10秒,为了看线程1和2打印变量的值
        Thread.sleep(10000L);

        System.out.println("结束");

    }
}

 执行结果

这是--Thread[t1,5,main]---true
这是--Thread[t2,5,main]---true
t21671368435004
这是11--Thread[t1,5,main]---false
这是222--Thread[t1,5,main]---false
结束

从上面可以看出线程2对变量改变后,线程1也能感知道。

3. 我们对案例1做一些变化,虽然没没有volatile修饰,但是我们在循环加一些代码,让代码执行时间长一些,等线程再次从线程内存中取值的时候,娶不到,就会去主内存中取值,这样也能感知到变量的变化。

package com.example.demovalid;

public class test3 {
    Boolean flag = true;
//    volatile Boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        test3 test1 = new test3();

        long currentTimeMillis = System.currentTimeMillis();

        Thread t1 = new Thread(() -> {

            System.out.println("这是--" + Thread.currentThread() + "---" + test1.flag);

            while (true) {

                try{
                // 这里抛异常,为了增加执行时间,延长下次从内存中去变量的值
                    int a = 1/0;
                }catch (Exception e){
// 这段打印是需要的,本人测试,去掉之后,还是不能跳出循环
                    System.out.println("出错以后");
                }

                if (!test1.flag) {
                    System.out.println("这是11--" + Thread.currentThread() + "---" + test1.flag);
                    break;
                }

            }
            System.out.println("这是222--" + Thread.currentThread() + "---" + test1.flag);

        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("这是--" + Thread.currentThread() + "---" + test1.flag);
            System.out.println("t2" + System.currentTimeMillis());
            test1.flag = false;

        }, "t2");
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();

        Thread.sleep(10000L);

        System.out.println("结束");

    }
}

执行结果

这是--Thread[t1,5,main]---true
出错以后
出错以后
.......
出错以后

这是--Thread[t2,5,main]---true
t21671368823608
出错以后
这是11--Thread[t1,5,main]---false
这是222--Thread[t1,5,main]---false

从执行结果看,在线程2改变了变量的值以后,线程1再次取值的时候,也感知到了变量的变化。 这就是因为,线程1再次去线程内存取值的时候,发现线程内存变量消失了,只能去主内存取值,然而,主内存的变量,已经被线程2修改了,所以线程1取到的是改变后的值

下面是另一个测试代码

package com.example.demovalid;

public class test2 {

// 主要不同是,这个变量的静态修改的,其实与上面的结果是一样的
    static volatile Boolean flag = true;
//    volatile Boolean flag = true;
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {

            System.out.println("这是--"+ Thread.currentThread() + "---" + flag);

            while (true){

                if (!flag){
                    System.out.println("这是11--"+ Thread.currentThread() + "---" + flag);
                    break;
                }

            }
            System.out.println("这是222--"+ Thread.currentThread() + "---" + flag);

        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("这是--"+ Thread.currentThread() + "---" + flag);
            System.out.println("t2" + System.currentTimeMillis());
            flag= false;

        }, "t2");
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();

        Thread.sleep(10000L);

        System.out.println("结束");

    }
}

另外附上参考文章

推荐看:Volatile关键字的作用_FighterLiu的博客-CSDN博客_volatile关键字的作用

有一个JMM内存模型的图: https://www.jb51.net/article/173207.htm

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值