Operação CAS em série de alta simultaneidade e análise de operação de baixo nível da CPU

CAS (Compare-and-Swap), ou seja, compare e substitua, é uma tecnologia comumente usada na implementação de algoritmos concorrentes.Muitas classes em pacotes simultâneos Java usam a tecnologia CAS. O CAS também é uma pergunta frequente em entrevistas Este artigo irá introduzir os princípios do CAS em profundidade.

1. Exemplo simples

Quando um método implementa i ++, como o método getAndIncrement1 () abaixo, ocorrerão problemas de segurança de simultaneidade quando chamados por vários threads. Neste momento, você pode usar a palavra-chave synchronized para bloquear explicitamente para garantir a segurança de simultaneidade.

No pacote de simultaneidade JAVA, muitas classes atômicas Atomic são fornecidas, o que também pode garantir a segurança do thread, como o método AtomicInteger.getAndIncrement () em getAndIncrement2 ().

package com.wuxiaolong.concurrent;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Description:
 *
 * @author 诸葛小猿
 * @date 2020-09-14
 */
public class Test1 {
    
    

    public static int i = 0;

    /**
     * 加锁保证多线程调用的并发安全
     * @return
     */
    public synchronized static int getAndIncrement1(){
    
    
        i++;
        return i;
    }

    /**
     * AtomicInteger是线程安全的  
     * @return
     */
    public static int getAndIncrement2(){
    
    
        AtomicInteger ai = new AtomicInteger();
        int in = ai.getAndIncrement();
        return in;
    }
}

2. O que é CAS

CAS: compare e troque ou compare e troque, comparação e interação. A função é garantir que a atualização de um valor por vários encadeamentos seja segura para encadeamentos sem um bloqueio.

Processo CAS: contém três parâmetros CAS (V, E, N), V representa a variável a ser atualizada, E representa o valor esperado e N representa o novo valor. Somente quando o valor V for igual ao valor E, o valor V seja definido como valor N, se o valor V for diferente do valor E, isso significa que outros encadeamentos fizeram atualizações e o encadeamento atual não faz nada.

Se você usar o CAS para implementar a operação i ++, poderá executar as seguintes etapas:

1. Leia o valor atual de i (como i = 0) e marque-o como E

2. Calcule o valor de i ++ como 1, denote-o como V

3. Leia o valor de i novamente e marque-o como N.

4. Se E == N, atualize o valor de i para 1, caso contrário, não atualize.

O processo de CAS é muito semelhante ao bloqueio otimista.O bloqueio otimista acredita que a probabilidade de problemas de segurança de thread é relativamente pequena, portanto, não há necessidade de adicionar bloqueios diretamente, mas comparar os dados originais ao atualizar os dados.

Na quarta etapa acima, se E == N, isso não significa que o valor de i não mudou. Talvez quando um thread executa a quarta etapa, outro thread muda i e, em seguida, muda de volta. Para o primeiro thread diga, Não sei da existência desse processo intermediário. Esse fenômeno é o problema ABA.

Como resolver o problema ABA? Na verdade, é muito simples. Adicione um campo de número de versão a i e adicione 1 ao número de versão sempre que i mudar. Além de comparar o valor de E sempre que i é atualizado, ele também compara se o número da versão é consistente. Isso resolve o problema ABA. No processo de desenvolvimento real, se o problema ABA não tiver impacto nos negócios, não há necessidade de considerar esse problema.

Terceiro, o uso de CAS no AtomicInteger

A implementação subjacente de CAS, a implementação subjacente de synchronized e a implementação subjacente de volatile são todas iguais. Usamos a classe AtomicInteger mencionada acima para ilustrar.

AtomicInteger é seguro para threads e geralmente se diz que AtomicInteger não tem bloqueio ou spinlock. Esta é a aplicação do CAS no JDK.

O código-fonte do método AtomicInteger.getAndIncrement ():

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
    
    
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

Código-fonte Unsafe.getAndAddInt ():

    public final int getAndAddInt(Object var1, long var2, int var4) {
    
    
        int var5;
        do {
    
    
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

Código-fonte Unsafe.compareAndSwapInt ():

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Este método é nativemodificado e a implementação do código-fonte não é visível no JDK. Como o código java é executado na JVM, a JVM da Oracle é Hotspot, se você quiser ver a implementação do método nativo, pode encontrar o código fonte do Hotspot, que é escrito em C e C ++. O código-fonte de Unsafe.java corresponde a unsafe.cpp no ​​código-fonte do Hotspot, que é escrito em C ++.

Quarto, a implementação subjacente do CAS

Para implementar o CAS, você deve entender o código-fonte do Hotspot. Você pode verificar o código do OpenJdk, você pode encontrar o código-fonte de várias versões aqui . Vamos pegar unsafe.cpp em jdk8u como exemplo para continuar o compareAndSwapIntmétodo de análise .

// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/share/vm/prims/unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

Você pode ver que o método Atomic :: cmpxchg é chamado, continue a analisar e encontrar este método:

// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/share/vm/runtime/atomic.cpp

jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
    
    
  assert(sizeof(jbyte) == 1, "assumption.");
  uintptr_t dest_addr = (uintptr_t)dest;
  uintptr_t offset = dest_addr % sizeof(jint);
  volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
  jint cur = *dest_int;
  jbyte* cur_as_bytes = (jbyte*)(&cur);
  jint new_val = cur;
  jbyte* new_val_as_bytes = (jbyte*)(&new_val);
  new_val_as_bytes[offset] = exchange_value;
  while (cur_as_bytes[offset] == compare_value) {
    
    
    //关键方法
    jint res = cmpxchg(new_val, dest_int, cur); 
    if (res == cur) break;
    cur = res;
    new_val = cur;
    new_val_as_bytes[offset] = exchange_value;
  }
  return cur_as_bytes[offset];
}

Várias arquiteturas de CPU em vários sistemas têm métodos de implementação relacionados. Os nomes de arquivos específicos são os seguintes:

// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/share/vm/runtime/atomic.inline.hpp

#ifndef SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
#define SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP

#include "runtime/atomic.hpp"

// Linux
#ifdef TARGET_OS_ARCH_linux_x86
# include "atomic_linux_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "atomic_linux_sparc.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "atomic_linux_zero.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "atomic_linux_arm.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "atomic_linux_ppc.inline.hpp"
#endif

// Solaris
#ifdef TARGET_OS_ARCH_solaris_x86
# include "atomic_solaris_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "atomic_solaris_sparc.inline.hpp"
#endif

// Windows
#ifdef TARGET_OS_ARCH_windows_x86
# include "atomic_windows_x86.inline.hpp"
#endif

// AIX
#ifdef TARGET_OS_ARCH_aix_ppc
# include "atomic_aix_ppc.inline.hpp"
#endif

// BSD
#ifdef TARGET_OS_ARCH_bsd_x86
# include "atomic_bsd_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "atomic_bsd_zero.inline.hpp"
#endif

#endif // SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
    

No diretório src / os_cpu /, há implementações de código de várias arquiteturas de CPU em vários sistemas. Entre elas, src / os_cpu / linux_x86 / vm é o código baseado na arquitetura x86 no Linux. A implementação final do método cmpxchg:

// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
    
    
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}    

Uma __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"parte do código é o núcleo, e asm se refere à linguagem assembly, que é uma linguagem de máquina que interage diretamente com a cpu.

LOCK_IF_MP significa "bloquear se houver várias CPUs" e MP significa Multi-Processadores. O programa decidirá se adiciona o prefixo de bloqueio à instrução cmpxchg de acordo com o número atual de processadores. Se o programa estiver sendo executado em vários processadores, adicione o prefixo de bloqueio (lock cmpxchg) à instrução cmpxchg. Por outro lado, se o programa estiver sendo executado em um único processador, o prefixo de bloqueio é omitido (um único processador manterá a consistência do pedido dentro do único processador, e o efeito de barreira de memória fornecido pelo prefixo de bloqueio não é necessário) .

// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp

// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

Resumindo

Como pode ser visto acima, a essência do CAS é:

lock cmpxchg instrução

Mas cmpxchgessa instrução de cpu em si não é atômica, ela ainda depende da lockinstrução anterior .

Siga a conta oficial e digite " java-summary " para obter o código-fonte.

Terminado, chame um dia!

[ Disseminação de conhecimento, compartilhamento de valor ], obrigado amigos por sua atenção e apoio, eu sou [ Zhuge Xiaoyuan ], um trabalhador migrante da Internet lutando pela hesitação.

Acho que você gosta

Origin blog.csdn.net/wuxiaolongah/article/details/108591678
Recomendado
Clasificación