O modelo de memória Java vem em sua própria

Adquira o hábito de escrever juntos! Este é o 17º dia da minha participação no "Nuggets Daily New Plan·April Update Challenge", clique para ver os detalhes do evento

Esta série de colunas  Coluna de programação concorrente Java - coluna do Yuanhao 875 - Nuggets (juejin.cn)

prefácio

Antes de falar sobre o modelo de memória Java, vamos falar de algo que é fácil de confundir: O modelo de memória Java aqui é o Modelo de Memória Java, que é um conjunto complexo de especificações, que é usado para resolver alguns problemas de programação concorrente.

E muitas pessoas também chamam a estrutura de memória de tempo de execução da JVM também chamada de modelo de memória Java, o que está muito errado, ou seja, a figura a seguir:

imagem.png

Estes são os vários espaços abertos pela JVM ao executar programas Java, incluindo as áreas de heap e métodos compartilhadas por threads (área de metadados ou geração permanente, que são tratadas de forma diferente por diferentes versões), bem como pilhas de métodos e contadores de programas que são não compartilhado por tópicos. Esta é a estrutura de memória JVM, não a chame de modelo de memória Java . Neste capítulo, entendemos realmente o que é o modelo de memória Java.

1650097935(1).jpg

texto

No último artigo, falamos sobre os três principais problemas que causam bugs de programação simultânea: visibilidade, atomicidade e ordem, e esses três problemas são os problemas que evoluíram ao longo das décadas de desenvolvimento de computadores, enquanto Java é uma linguagem de programação avançada. A linguagem suporta simultaneidade, então para resolver os problemas causados ​​pela visibilidade e ordem, a linguagem Java introduz o famoso modelo de memória Java .

O que é o modelo de memória Java

No artigo anterior, dissemos que a fonte do problema de visibilidade é o cache da CPU, e a fonte da ordem é a otimização do compilador, então posso simplesmente desabilitar o cache da CPU e a otimização do compilador, mas embora o problema seja resolvido, as pessoas também Sem ele, o desempenho do programa será preocupante.

Uma solução razoável é desabilitar o cache e a otimização de compilação sob demanda. Quando é necessário? Este é o programador que escreve o código, então seria bom fornecer aos programadores uma maneira de desabilitar o cache e a otimização de compilação conforme necessário. , e o O modelo de memória Java faz exatamente isso.

Java内存模型定义了一套规范,能使JVM按需禁用CPU缓存和编译优化;而对于程序员来说,就是提供了一些方法可以让JVM按需禁用缓存和编译优化,这些方法包括了volatile、synchronized和final三个关键字,以及六项Happen-Before规则。

使用volatile的困惑

volatile在古老的C语言中就有,最原始的意义就是禁用CPU缓存,例如我们声明一个volatile变量volatile int x = 0,这句话的意思对这个变量的读写,不能使用CPU缓存,必须从内存中读取或者写入。

那如果只有这个功能的话也只能说明这个变量是线程间可见的,但是还不够完全解决问题,我们来看下面代码:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}
复制代码

假如线程A执行writer()方法,会把变量v的值为true写入内存,线程B执行reader()方法,按照volatile语义,线程B会从内存中获取变量v,如果线程B看到的是"v == true",这里x值可能是42也可能是0,这里要分版本:低于1.5版本上,x值可能是42,也可能是0;如果1.5以上的版本,x只能是42。

产生这个原因也非常简单,变量x可能被CPU缓存导致可见性问题,也有可能是指令重排导致的,但是在1.5以上对volatile语义进行了增强,如何增强呢 就是Happens-Before规则。

Happens-Befroe规则

Happen-Before规则是Java内存模型制定的规则,用来处理线程间可见性问题,至于如何去处理,我们先不做讨论细节。

这个Happen-Before可以说是Java内存模型中最难懂的地方,理解起来非常绕;首先这个词的翻译就比较难,Happen-Before并不是说前面一个操作发生在后续操作之前,它要真正表达的意思是:前面一个操作的结果对于后续操作是可见的

就像有心灵感应的2个人,虽然远隔千里,一个人所想,另一个人能看得见,而Happens-Before规则就是要线程之间保持这种"心灵感应",所以比较正式的说法是:Happens-Berfore约束了编译器的优化行为,允许编译器优化,但是优化后必须遵守Happens-Before规则

说到这里或许就明白了,虽然volatile变量禁用了CPU缓存,但是没有禁止编译器优化啊,编译器依旧可以优化,但是像前面说的把"x = 42和v = true"给调换位置的优化就不会,而且x可见性也能得到保证,那这个强大的Happen-Before规则是什么样的呢。

和程序员相关的规则有6个,且都关于可见性的。

(1) 程序的顺序性规则

指的是在一个线程中按照程序顺序,前面的操作Happens-Berfore于后续的任意操作。比如前面的代码中,第6行代码"x = 42" Happens-Before于第7行代码"v = true",这比较符合单线程的思维:程序前面对某个变量的修改,一定是对后续操作可见的。

注意哦,这个规则是单线程下的规则,比如上面代码如果没有volatile修饰的话,x和v的赋值之间是没有依赖的,所以这2个赋值操作可以重排,但是x的赋值结果却对v的赋值这条语句来说是可见的,虽然这个可见性没啥用(因为v的赋值不依赖x的值)。

(2) volatile变量规则

这条规则是指一个volatile变量的写操作,Happens-Before于后续对这个volatile变量的读操作,单独看这一条规则,这不就是禁用缓存的意思吗,别急,我们看第三条。

(3) 传递性规则

这条规则是指如果A Happens-Berfore B,且B Happens-Before C,那么A Happens-Before C,那我们将规则3的传递性应用到我们的例子中是:

imagem.png

可以看到:

  • "x = 42" Happens-Before 写变量"v = true",这是规则1;

  • 写变量"v = true" Happens-Before 读变量"v = true",这是规则2;

再根据传递性规则,我们得到结果"x = 42" Happens-Before读变量"v = true",这意味什么呢 那就是线程A设置的"x = 42"是对线程B可见的,也就是线程B能看到"x == 42",这就是1.5版本对volatile语义的增强,这个意义重大,Java并发工具包就是靠volatile语义来搞定可见性的。

而这里对这个可见性的实现是禁止这2段语句的重排,这个也是volatile的通俗功能说法会禁止指令重排序。

(4) 管程中锁的规则

这条规则是指一个锁的解锁Happens-Before于后续对这个锁的加锁

管程是一种通用的同步原语,在Java中是指synchronized,synchronized是Java里对管程的实现,管程中的锁在Java里隐式实现的,比如下面代码在进入同步代码块之前,会自动加锁,而在代码块执行完后会自动释放锁,加锁以及释放锁都是编译器帮我实现的:

synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
    this.x = 12; 
  }  
} //此处自动解锁
复制代码

可以这样理解:假设x初始值为10,线程A执行完代码块后,x值为12,自动释放锁,线程B进入代码块时,能够看见线程A对x的写操作,即线程B能够看到x==12,这也是符合我们对synchronized的用法。

(5) 线程start()规则

Esta regra é sobre a inicialização do thread, ou seja, após o thread principal A iniciar o thread filho B, o thread filho B pode ver a operação do thread principal antes de iniciar o thread filho B , como o código a seguir:

Thread B = new Thread(()->{
  // 主线程调用B.start()之前
  // 所有对共享变量的修改,此处皆可见
  // 此例中,var==77
});
// 此处对共享变量var修改
var = 77;
// 主线程启动子线程
B.start();
复制代码

Isso também está de acordo com o nosso senso comum, ou seja, a operação start() do Happens-Before em qualquer operação da thread B.

(6) Regras de junção de thread()

Esta regra é sobre espera de thread, ou seja, a thread principal A espera a conclusão da sub-thread B, ou seja, a thread principal A chama o método join() da sub-thread B. Quando a sub-thread B é completado, o thread principal pode ver a operação do sub-thread, que é chamado aqui.Vendo também se refere a operações em variáveis ​​compartilhadas .

Por exemplo o seguinte código:


Thread B = new Thread(()->{
  // 此处对共享变量var修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用B.join()之后皆可见
// 此例中,var==66
复制代码

Este é o retorno de qualquer operação Acontece-Antes na thread B para a operação join().

Resumir

O conteúdo deste capítulo é relativamente complicado, apresenta principalmente o modelo de memória Java para resolver os problemas de visibilidade e ordem mencionados anteriormente. A regra Happens-Before e as palavras-chave voláteis e sincronizadas podem resolver os problemas de visibilidade e ordenação.No próximo artigo, veremos como resolver o problema atômico.

Acho que você gosta

Origin juejin.im/post/7087488770420768782
Recomendado
Clasificación