O conceito de travamento foi mencionado na introdução de [alta concorrência Java 1]. Como há um grande número de aplicativos sem bloqueio no código-fonte do JDK, o bloqueio é introduzido aqui.
1 Explicação detalhada do princípio da classe sem trava
1.1 CAS
O processo do algoritmo CAS é o seguinte: ele contém 3 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 se v
Quando o valor é igual ao valor E, o valor de V será definido como N. Se o valor V for diferente do valor E, significa que outros threads já fizeram atualizações e o thread atual não faz nada. Finalmente, o CAS retorna o valor verdadeiro das operações atuais do V. CAS é realizado com uma atitude otimista e sempre acredita que pode concluir com êxito as operações. Quando vários threads operam uma variável usando CAS ao mesmo tempo, apenas um ganhará e atualizará com sucesso e o restante falhará. O encadeamento falhado não será suspenso, é informado apenas de que a falha é permitida e é permitido tentar novamente e, é claro, o encadeamento falhado também permitirá que a operação seja abandonada. Com base neste princípio, CAS
A operação é bloqueada imediatamente e outros threads também podem detectar interferência no encadeamento atual e manipular -o adequadamente.
Vamos descobrir que existem muitos passos no CAS. É possível que, depois de julgar que V e E sejam iguais, quando estamos prestes a atribuir valores, trocamos o encadeamento e alteramos o valor. O que causou inconsistência de dados?
De fato, essa preocupação é redundante. Todo o processo de operação do CAS é uma operação atômica, que é concluída por uma instrução da CPU.
1.2 Instruções da CPU
A instrução da CPU do CAS é cmmpxchg
O código de instrução é o seguinte:
/ * acumulador = al, ax ou eax, dependendo de uma comparação de byte, palavra ou palavras duplas está sendo executada */ if (acumulator == Destination) {zf = 1; Destino = fonte; } else {zf = 0; acumulador = destino; } Se o valor alvo for igual ao valor no registro, um sinalizador de salto será definido e os dados originais serão definidos como o destino. Se você não esperar, não definirá a bandeira de salto.
O Java oferece muitas classes sem bloqueio, então vamos introduzir classes sem bloqueio abaixo.
2 inútil
Já sabemos que a trava é muito mais eficiente do que o bloqueio. Vamos dar uma olhada em como o Java implementa essas classes sem trava.
2.1. Atomicinteger
AtomicInteger, como o número inteiro, ambos herdam a classe numérica
A classe pública Atomicinteger estende o número implementa Java.io.Serializable
Existem muitas operações de CAS no AtomicInteger, das quais são típicas:
Public final boolean Comparansset (int espera, int update) {
return insefa.compareandswapint (isto, valueoffset, espera, atualização);
}
Aqui vamos explicar o método inseguro.compareandswapint. Isso significa que, se o valor da variável cuja deslocamento nessa classe for ValueOffSet for o mesmo que o valor esperado, defina o valor dessa variável a ser atualizado.
De fato, a variável com o trueOffset de deslocamento é valor
static {try {valueOffSet = insefa.ObjectFieldOffset (atomicinteger.class.getDecaredfield ("value")); } catch (Exceção ex) {lança novo erro (ex); }}Dissemos antes que o CAS possa falhar, mas o custo da falha é muito pequeno, portanto a implementação geral está em um loop infinito até que seja bem -sucedido.
public final int getAndIncrement () {for (;;) {int current = get (); int próximo = corrente + 1; if (comparaandndSet (atual, próximo)) retornar corrente; }}2.2 Inseguro
Do nome da classe, podemos ver que operações inseguras são operações não seguras, como:
Defina o valor de acordo com o deslocamento (eu vi essa função no atomicinteger acabou de introduzir)
Park () (pare este tópico, ele será mencionado no Blog Future)
A operação CAS subjacente API não pública pode diferir bastante em diferentes versões do JDK.
2.3. Atomicreferência
Atomicinteger foi mencionado anteriormente e, claro, atomicbooliano, atômico etc. são todos semelhantes.
O que queremos apresentar aqui é atomicreferência.
AtomicReference é uma classe de modelo
Classe pública AtomicReference <V> implementa Java.io.Serializable
Pode ser usado para encapsular qualquer tipo de dados.
Por exemplo, string
teste de pacote; importar java.util.Concurrent.atomic.atomicreference; public class Test {public final estático atomicreference <string> atomicString = new AtomicReference <string> ("mangueira"); public static void main (string [] args) {for (int i = 0; i <10; i ++) {final int num = i; novo thread () {public void run () {try {thread.sleep (math.abs ((int) math.random ()*100)); } catch (Exceção e) {e.printStackTrace (); } if (atomicString.compareandset ("Hosee", "ztk")) {System.out.println (thread.currentThread (). getId () + "alteração valor"); } else {System.out.println (thread.currentThread (). getId () + "falhou"); }}; }.começar(); }}}resultado:
10Failed
13Failed
Valor 9 da troca
11 falhou
12Failed
15filed
17Failed
14Failed
16Failed
18Failed
Você pode ver que apenas um thread pode modificar o valor e os encadeamentos subsequentes não podem mais modificá -lo.
2.4.AtomicstampedReference
Vamos descobrir que ainda há um problema com a operação do CAS.
Por exemplo, o método de incremento eget de atomicinteger anterior
public final int incrementAndget () {for (;;) {int current = get (); int próximo = corrente + 1; if (comparaandsset (atual, próximo)) retornar a seguir; }} Suponha que o valor atual = 1 Quando um thread int current = get () é executado, alterne para outro thread, este thread transforma 1 em 2 e depois outro thread transforma 2 em 1 novamente. Neste momento, mude para o encadeamento inicial. Como o valor ainda é igual a 1, as operações do CAS ainda podem ser executadas. Obviamente, não há nenhum problema com a adição. Se houver alguns casos, esse processo não será permitido quando for sensível ao estado dos dados.
Neste momento, a classe AtomicstampedReference é necessária.
Ele implementa uma classe de par internamente para encapsular valores e registros de data e hora.
Classe estática privada par <t> {referência final t; Final Int Stamp; par privado (referência t, int selo) {this.reference = reference; this.stamp = carimbo; } estático <t> par <t> de (t referência, int carim) {return new par <t> (referência, carimbo); }}A idéia principal desta classe é adicionar registros de data e hora para identificar cada alteração.
// Os parâmetros de comparação de configurações são: o valor esperado escreve o novo valor Espere o registro de data e hora novo
public boolean ComparaNDSet (v Espera -Reference, v newReference, int esperastamp, int newstamp) {par <V> current = par; return whreverreference == Current.Reference && WeerthStamp == Current.stamp && ((newReference == current.Reference && newstamp == current.stamp) || caspair (atual, par.of (newReference, newstamp))); } Quando o valor esperado é igual ao valor atual e o registro de data e hora esperado é igual ao registro de data e hora atual, o novo valor é gravado e o novo registro de data e hora é atualizado.
Aqui está um cenário que usa atomicstampedReference. Pode não ser adequado, mas não consigo imaginar um bom cenário.
O plano de fundo da cena é que uma empresa recarrega os usuários com baixo equilíbrio gratuitamente, mas cada usuário só pode recarregar uma vez.
teste de pacote; importar java.util.Concurrent.atomic.atomicstampedReference; public class Test {static atomicstampedReference <Teger> Money = new AtomicstampedReference <Teger> (19, 0); public static void main (string [] args) {for (int i = 0; i <3; i ++) {final int timestamp = Money.getStamp (); novo thread () {public void run () {while (true) {while (true) {Integer m = Money.getReference (); if (m <20) {if (Money.compareandset (m, m + 20, registro de data e hora, timestamp + 1)) {System.out.println ("Recarregue com sucesso, equilíbrio:" + Money.getReference ()); quebrar; }} else {break; }}}}}}; }.começar(); } new Thread () {public void run () {for (int i = 0; i <100; i ++) {while (true) {int timestamp = Money.getStamp (); Inteiro M = Money.getReference (); if (m> 10) {if (Money.compareandset (m, m - 10, registro de data e hora, timestamp + 1)) {System.out.println ("Consumido 10 yuan, Balance:" + Money.getReference ()); quebrar; }} else {break; }} tente {thread.sleep (100); } catch (Exceção e) {// TODO: lidar com exceção}}}}; }.começar(); }}Explique o código, existem 3 threads recarregam o usuário. Quando o saldo do usuário for menor que 20, recarregue o usuário 20 yuan. Existem 100 fios consumindo, cada um gastando 10 yuan. O usuário inicialmente possui 9 yuan. Ao usar o AtomicstampedReference para implementá -lo, o usuário será recarregado apenas uma vez, porque cada operação causa o registro de data e hora +1. Resultados em execução:
Recarregue com sucesso, equilíbrio: 39
Consumo de 10 yuan, equilíbrio: 29
Consumo de 10 yuan, equilíbrio: 19
Consumo de 10 yuan, equilíbrio: 9
Se você usar atomicreferência <inteiro> ou número inteiro atômico para implementá -lo, isso causará várias recargas.
Recarregue com sucesso, equilíbrio: 39
Consumo de 10 yuan, equilíbrio: 29
Consumo de 10 yuan, equilíbrio: 19
Recarregue com sucesso, equilíbrio: 39
Consumo de 10 yuan, equilíbrio: 29
Consumo de 10 yuan, equilíbrio: 19
Recarregue com sucesso, equilíbrio: 39
Consumo de 10 yuan, equilíbrio: 29
2.5. AtomicintegerRay
Comparado ao AtomicInteger, a implementação de matrizes é apenas um subscrito extra.
Public final boolean Comparansset (int i, int espera, int update) {
Retornar ComparandStraw (CheckEdByTeoffset (i), espere, atualização);
}
Seu interior apenas encapsula uma matriz normal
Private Final Int [] Array;
O interessante aqui é que os principais zeros dos números binários são usados para calcular o deslocamento na matriz.
Shift = 31 - Integer.NumberOfleadingZeros (escala);
O zero principal significa que, por exemplo, 8 bits representam 12.00001100, então o zero principal é o número de 0 na frente de 1, que é 4.
Como calcular o deslocamento não é introduzido aqui.
2.6. AtomicintegerfieldUpdater
A principal função da classe AtomicIntegerfieldUpdater é permitir que variáveis comuns também desfrutem de operações atômicas.
Por exemplo, havia originalmente uma variável que era do tipo int, e essa variável foi aplicada em muitos lugares. No entanto, em um determinado cenário, se você quiser transformar o tipo int em AtomicInteger, se você alterar o tipo diretamente, precisará alterar o aplicativo em outros lugares. AtomicintegerfieldUpdater foi projetado para resolver esses problemas.
teste do pacote; importar java.util.concurrent.atomic.atomicinteger; importar java.util.concurrent.atomic.atomicintegerfieldUpDater; public class Test {public static class V {int id; Pontuação INT volátil; public int getScore () {Return Score; } public void SetScore (Int Score) {this.score = score; }} public final estático atomicintegerfieldUpDater <V> vv = atomicintegerfieldUpdater.newupDater (v.class, "score"); public static atomicinteger allscore = new atomicinteger (0); public static void main (string [] args) lança interruptedException {final v stu = new v (); Thread [] t = novo thread [10000]; para (int i = 0; i <10000; i ++) {t [i] = new Thread () {@Override public void run () {if (Math.random ()> 0.4) {vv.inCreremendget (stu); allScore.incremendget (); }}}; t [i] .start (); } para (int i = 0; i <10000; i ++) {t [i] .Join (); } System.out.println ("Score ="+Stu.getScore ()); System.out.println ("AllScore ="+AllScore); }} O código acima transforma a pontuação usando atomicintegerfieldUpdater em atomicinteger. Garanta a segurança do thread.
AllScore é usado aqui para verificar. Se a pontuação e os valores AllScore forem os mesmos, significa que é seguro para threads.
Observação: