掌握Java的内存模型,你就是解决并发问题最靓的仔


摘要:如果编写的并发程序出现问题时,很难通过调试来解决相应的问题,此时,需要一行行的检查代码,这个时候,如果充分理解并掌握了Java的内存模型,你就能够很快分析并定位出问题所在。

本文分享自华为云社区《 【高并发】如何解决可见性和有序性问题?这次彻底懂了!》,作者:冰 河 。

今天,我们先来看看在Java中是如何解决线程的可见性和有序性问题的,说到这,就不得不提一个Java的核心技术,那就是——Java的内存模型。

如果编写的并发程序出现问题时,很难通过调试来解决相应的问题,此时,需要一行行的检查代码,这个时候,如果充分理解并掌握了Java的内存模型,你就能够很快分析并定位出问题所在。

什么是Java内存模型?

在内存里,Java内存模型规定了所有的变量都存储在主内存(物理内存)中,每条线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行。不同的线程无法访问其他线程的工作内存里的内容。我们可以使用下图来表示在逻辑上 线程、主内存、工作内存的三者交互关系。

现在,我们都理解了缓存导致了可见性问题,编译优化导致了有序性问题。也就是说解决可见性和有序性问题的最直接的办法就是禁用缓存和编译优化。但是,如果只是简单的禁用了缓存和编译优化,那我们写的所谓的高并发程序的性能也就高不到哪去了!甚至会和单线程程序的性能没什么两样!有时,由于竞争锁的存在,可能会比单线程程序的性能还要低。

那么,既然不能完全禁用缓存和编译优化,那如何解决可见性和有序性的问题呢?其实,合理的方案应该是按照需要禁用缓存和编译优化。什么是按需禁用缓存和编译优化呢?简单点来说,就是需要禁用的时候禁用,不需要禁用的时候就不禁用。有些人可能会说,这不废话吗?其实不然,我们继续向下看。

何时禁用和不禁用缓存和编译优化,可以根据编写高并发程序的开发人员的要求来合理的确定(这里需要重点理解)。所以,可以这么说,为了解决可见性和有序性问题,Java只需要提供给Java程序员按照需要禁用缓存和编译优化的方法即可。

Java内存模型是一个非常复杂的规范,网上关于Java内存模型的文章很多,但是大多数说的都是理论,理论说多了就成了废话。这里,我不会太多的介绍Java内存模型那些晦涩难懂的理论知识。 其实,作为开发人员,我们可以这样理解Java的内存模型:Java内存模型规范了Java虚拟机(JVM)如何提供按需禁用缓存和编译优化的方法。

说的具体一些,这些方法包括:volatile、synchronized和final关键字,以及Java内存模型中的Happens-Before规则。

volatile为何能保证线程间可见?

volatile关键字不是Java特有的,在C语言中也存在volatile关键字,这个关键字最原始的意义就是禁用CPU缓存。

例如,我们在程序中使用volatile关键字声明了一个变量,如下所示。

volatile int count = 0

此时,Java对这个变量的读写,不能使用CPU缓存,必须从内存中读取和写入。

蓝色的虚线箭头代表禁用了CPU缓存,黑色的实线箭头代表直接从主内存中读写数据。

接下来,我们一起来看一个代码片段,如下所示。

【示例一】

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 1;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //x的值是多少呢?
    }
  }
}

以上示例来源于:点击关注,第一时间了解华为云新鲜技术~