Java并发编程(一):volatile关键字与内存可见性

volatile关键字与内存可见性

为什么要利用多线程?

其实就是为了提高效率,尽可能去利用系统/CPU的资源。

但使用不当可能会造成性能更低。

因为涉及到 线程调度,上下文切换问题,线程创建销毁等问题,所以在用多线程时有许多注意事项。

主要关注 JUC包

volatile 关键字

来看下面一段代码

public class TestVolatile {
    public static void main(String[] args)
    {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();

        while (true)
        {
            if(td.isFlag()){
                System.out.println("----------------");
                break;
            }
        }
    }


}
class ThreadDemo implements Runnable{
    private boolean flag = false;
    @Override
    public void run() {
        try {
            //让该线程慢于主方法线程执行
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag =true;
        System.out.println("flag="+isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

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

}

代码逻辑:

  • 线程对象 td 改变其中的flag变量为true
  • main方法(也是个线程)判断如果td对象的flag属性为true,则跳出循环

实际上运行结果:

main方法一直没有退出,一直在while循环中

发生了什么?

线程 tdflag变量为true了,而main方法去读flag依然是false

内存可见性

下面介绍一下JVM的内存模型

Java内存模型

  • 共享内存的并发模型,线程间通过读-写共享变量(堆内存中的实例域,静态域和数组元素)来完成隐式通信

  • 计算机高速缓存和缓存一致性
    • CPU(高速)——缓存——内存(低速),有可能有多个处理器(核),会导致缓存不一致,处理器访问回缓存时需要遵循一些协议
  • JVM主内存与工作内存
    • 线程A先把本地内存A更新的共享变量刷新到主内存中
    • 线程B到主内存中读线程A之前已更新过的共享变量
    • 这样线程AB就完成了通信
  • 也就是说,main方法刚开始从主内存拿到的flagfalse,但后面td线程将flag改成了true

  • 但此时main方法依然处在while循环中,由于while循环执行效率很高,所以没办法在两次循环之间抽出时间从主内存中再次读取数据

总的来说,当多个线程操作共享数据时,彼此不可见,这就是 内存可见性问题

可以用同步锁来解决,在while循环体里加上synchronized关键字,对线程进行加锁,但效率很低,线程很多时,会造成很严重的阻塞等待

怎么解决?

使用volatile关键字

  • 当多个线程进行操作共享数据时,可以保证内存中的数据是可见的
  • 即如果加了volatile关键字,任何线程都要从主内存中读取最新的数据
  • 性能影响:稍微下降,禁止了指令重排序

上面的代码改动如下:

public class TestVolatile {
    public static void main(String[] args)
    {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();

        while (true)
        {
            if(td.isFlag()){
                System.out.println("----------------");
                break;
            }
        }
    }


}
class ThreadDemo implements Runnable{
    //加上了volatile关键字修饰
    private volatile boolean  flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag =true;
        System.out.println("flag="+isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

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

发现运行后,main方法成功退出

其他问题
  • volatile仅仅是一种较为轻量级的同步策略
  • volatile不具备**“互斥性”**
  • volatile无法保证变量的原子性
原创文章 40 获赞 16 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43925277/article/details/105116535