Java多线程之可见性(一)

可见性:

引言:上一篇文章我们详细地讲了一下什么叫原子性,其中提到了用互斥变量来控制两个线程对缓冲区的访问,即wait()和signal()操作。
我们说在一个线程执行wait()操作进入临界区之后,对于另一个想访问的线程来说,由于mutex=0而进入不了临界区,这其实是不准确的。在其他线程看来,此时mutex值还可能是1。那么问题究竟出在哪呢,其实就是可见性问题。
有一个很重要的例子,摘自《Effective Java》第6条,来和大家一起进入可见性的学习。

public class StopThread {
    private static boolean stopRequested;

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

        Thread backgroundThread = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                int count=0;
                while(!stopRequested) {
                    count++;
                }
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

OK,代码很简单,我们分析一下。程序里面总共两个线程,一个主线程(main),一个子线程(backgroundThread)。StopThread有一个共享的布尔变量stopRequested。子线程通过判断该布尔变量来决定是否执行循环块代码,即count++操作。那么,在我们开始主线程之后,程序大概会经历多久停下来呢?(注意静态变量stopRequested默认是false)。你可能会说,由于主线程停止了1秒后才修改的布尔变量为true,所以,子线程大概会经过1秒执行完整个run()方法,也就是说整个程序大概会运行1秒钟。直接看结果吧:这个循环将一直执行下去!!!

这个问题的根源就在于多线程对共享变量的可见性!!
还是先来书上的定义:如果一个线程对于某个共享变量的进行更新之后,后续访问该变量的线程可以读取到该更改的结果,那么我们就说这个线程对于共享变量的的更新是可见的。
有点基础的朋友可能就恍然大悟,对于初次接触的同学还不好理解。问题就出在java的内存模型上!!!

先明白几点内存知识:

1.线程都有自己私有的工作区域(通常指寄存器),区别于主存或高速缓存。
2.这种设计的原因是,访问主存的速率是很慢的,线程要用共享变量时是将共享变量拷贝一份到自己的私有内存,之后再更新回去。
3.因此这就涉及到什么时候把值更新回去,以及其他线程看到的是不是更新后的值。
开篇的例子中出的问题在于:子线程并没有到内存里面去读新值,所以一直跳不出循环。这里还涉及jit编译器对循环体热点代码的优化,有兴趣的同学可以自己去查一下。

好了,问题已经给大家说清楚了,那么我们要怎么去解决这个问题呢?
其实很简单,就是让线程在读取共享变量时都到主存去取这个新值,在修改了这共享变量后都及时地写回主存就好了。
这里给大家介绍一个关键字volatile,我以一个看似过来人的语气告诉你,这是面试官钟爱的考点,嘿嘿>o<!
所以我们只需要用volatile修饰stopRequested就可以了。大家可以试一试哦,嗯,关于底层实现下一次再讲,回寝室!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值