java多线程实战学习1---可见性

前言

最近在看java并发实战,受益匪浅,但是有觉得有种囫囵吞枣的感觉,工作以后的学习,没有学校里面的系统,第一,没有老师教,第二,没有进度规划,第三,眼高手低缺失实战。
打算写这个微博系列,目的一,监督自己学习,目的二,将经验和教训与大家共享。

可见性

可见性是jvm的内存机制引入的问题,时间和空间用于都是一个矛盾的话题,为了提升效率,每个线程的内存和主内存直接存在一个同步过程。
具体可以参考java内存分布

我们看下书中的例子

public class NoVisibility {
    private static boolean ready;
    private static int number;

    public static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println("number:" + number);
        }
    }

    public static void main(String[] args) {
        ReaderThread readerThread = new ReaderThread();
        readerThread.start();
      
        number = 42;
        ready = true;
    }
}

按书中描述,可能会死循环,可能会打印出来number的值为0。
但是,一直只会打印number:42,所有尝试只有这一种结果。
但是很遗憾,我这边试了很久,包括不断创建线程测试,还是没有复现出来。
是书中讲错了,还是我们试验的方式不对。

思路一 两个线程速度问题

主线程运行完,子线程才启动,导致子线程每次读取的值,已经是变化的。修改代码如下:

public class NoVisibilitySleep {
    private static boolean ready;
    private static int number;

    public static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println("number:" + number);
        }
    }

    public static void main(String[] args) {
        ReaderThread readerThread = new ReaderThread();
        readerThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        number = 42;
        ready = true;
    }

}

结果还是一样,number:42

思路二 Thread.yield的作用

去掉Thread.yield

public class NoVisibilitySleepWithoutYield {
    private static boolean ready;
    private static int number;

    public static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
//                Thread.yield();
            }
            System.out.println("number:" + number);
        }
    }

    public static void main(String[] args) {
        ReaderThread readerThread = new ReaderThread();
        readerThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        number = 42;
        ready = true;
    }

}

这个时候神奇的事情发生了,这个程序永远是个死循环。(两个条件同时满足的时候就会出现死循环)

但是,如果你用debug模式在while地方打断点,或者是在循环里面加入System.out.println之类的话,也通用不会出现死循环。
理论上打印不应该改变程序的,但是这样奇怪的现象出现了。
有三篇文章关于这个问题:

  1. R大在知乎上的 R大解释
  2. csdn上的文章 趣谈可见性
  3. 可见性理解
    其中文章1和3的大部分观点是一致的,对应死循环的原因是,如果while里面是个空,或者是number++之类的,编译器会进行优化代码变成了
 int localReady = ready
  while (!localready) {
   }

localReady只会赋值一次,所以就会导致死循环,加入了System.out.println编译器没有优化,所以就会输出结果。

可见性和代码优化是同一件事情不同表达方式,正是由于jvm自己的内存结构,会对代码进行相应的优化。可见性,主内存和线程内存的拷贝是底层数据的表现形式,代码优化是具体实现手段。

所以,书中说了可能永远不可见,指的就是这种情况。

但是为什么Thread.yield和Thread.sleep在循环中出现,程序能够直接运行,没有一个准确的答案,官方说是不会影响的,但是现实中确实如此。

上面的例子证明了可见性了么?其实没有完全证明,要想按书上的例子证明其实很困难,依赖于环境,jvm版本,参数配置等等,但是理论上是完全有可能出现的。
笔者的可以通过另外一个例子发现可见性java多线程学习3-重排序

总结

目前无法复现书中例子,原因我目前没有探明,需要查询更多资料得到原因。
得出结论是,对于这种标志位形式的变量,一般只有set和get等原子操作的,引入引入关键字volitate,可以让代码不优化,时刻读取主内存的元素。
但是volitate只能保证可见性,对于原子性无法保证。下次我们在看看原子性

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值