JUC简笔4-volatile内存可见性

首先来看一段代码

public class Solution {
     public static void main(String[] args) {
         ThreadDemo td = new ThreadDemo();
         new Thread(td).start();
         while(true) {
             //判断td中flag的值
             if(td.isFlag())
                 System.out.println("main:flag is true");
         }
     }
}

class ThreadDemo implements Runnable {
    private boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
            flag = true;
            System.out.println("Thread1:now flag is true");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

运行结果如下:
《JUC简笔4-volatile内存可见性》
从结果可以看出,在线程td中已经令flag的值为true,但在main线程中获得的flag值却为false;

原因分析:
《JUC简笔4-volatile内存可见性》
在程序运行时,JVM为了提高运行效率,会给每条线程分配一小块空间作为缓存(上图中的cache);程序开始运行时,在主内存(Main Memory)中初始化flag=false;当td修改flag为true时,先把flag的值从主内存复制到cache中,再把cache中修改flag=true,但并没有立刻同步到主内存中,所以Main线程获取到的flag为false;又由于Main线程中while(true)执行的速度很快又一直在循环,之后就算主内存中flag的值已经修改为true,也没有机会把flag的值从主内存中同步过来。

这里就引出了内存可见性问题,多条线程之间对于共享数据的操作,彼此是不可见的。

此问题可以通过加锁解决,加入synchronized中可以保证每次获取flag时都是在主内存中最新的值,但加锁必然会导致程序的运行效率下降,代码如下:

public class Solution {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            //加锁
            synchronized (td) {
                // 判断td中flag的值
                if (td.isFlag()) {
                    System.out.println("main:flag is true");
                    break;
                }
            }
        }
    }
}

class ThreadDemo implements Runnable {
    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
            flag = true;
            System.out.println("Thread1:now flag is true");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

最好还是用volatile修饰flag:

public class Solution {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            synchronized (td) {
                // 判断td中flag的值
                if (td.isFlag()) {
                    System.out.println("main:flag is true");
                    break;
                }
            }
        }
    }
}

class ThreadDemo implements Runnable {
    //flag加上volatile修饰
    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
            flag = true;
            System.out.println("Thread1:now flag is true");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
2)禁止进行指令重排序
3)volatile不保证原子性(具体可参考JUC简笔2-多线程下的非原子操作)

    原文作者:JUC
    原文地址: https://blog.csdn.net/weixin_38278878/article/details/80880577
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞