美文网首页
Java多线程之可见性(一)

Java多线程之可见性(一)

作者: NEU_PROYZ | 来源:发表于2018-05-05 22:09 被阅读8次

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

    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就可以了。大家可以试一试哦,嗯,关于底层实现下一次再讲,回寝室!

    相关文章

      网友评论

          本文标题:Java多线程之可见性(一)

          本文链接:https://www.haomeiwen.com/subject/pbwerftx.html