Quando usamos AtomicReference
?
É necessário criar objetos em todos os programas multithread?
Forneça um exemplo simples em que AtomicReference deve ser usado.
Quando usamos AtomicReference
?
É necessário criar objetos em todos os programas multithread?
Forneça um exemplo simples em que AtomicReference deve ser usado.
Respostas:
A referência atômica deve ser usada em uma configuração na qual você precisa executar operações atômicas simples (por exemplo, com segurança de thread , não triviais) em uma referência, para a qual a sincronização baseada em monitor não é apropriada. Suponha que você queira verificar se um campo específico apenas se o estado do objeto permanece como você verificou pela última vez:
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
Por causa da semântica de referência atômica, você pode fazer isso mesmo se o cache
objeto for compartilhado entre encadeamentos, sem usar synchronized
. Em geral, é melhor usar sincronizadores ou a java.util.concurrent
estrutura, em vez de usar a Atomic*
menos que saiba o que está fazendo.
Duas excelentes referências de árvores mortas que apresentarão esse tópico:
Observe que (não sei se isso sempre foi verdade) a atribuição de referência (por exemplo =
) é atômica (atualizando tipos primitivos de 64 bits como long
ou double
pode não ser atômica; mas a atualização de uma referência é sempre atômica, mesmo que seja de 64 bits ) sem usar explicitamente um Atomic*
.
Consulte a Especificação de linguagem Java 3ed, Seção 17.7 .
AtomicReference
, marque a variável volatile
porque, embora o tempo de execução garanta que a atribuição de referência seja atômica, o compilador pode executar otimizações sob o pressuposto de que a variável não estava sendo modificada por outros threads.
AtomicReference
"; se você estiver usando, meu conselho seria ir na direção oposta e marcá-lo final
para que o compilador possa otimizar adequadamente.
Uma referência atômica é ideal para usar quando você precisa compartilhar e alterar o estado de um objeto imutável entre vários encadeamentos. Essa é uma afirmação super densa, então vou descrevê-la um pouco.
Primeiro, um objeto imutável é um objeto que não é efetivamente alterado após a construção. Freqüentemente, os métodos de um objeto imutável retornam novas instâncias dessa mesma classe. Alguns exemplos incluem as classes de wrapper Long e Double, bem como String, apenas para citar alguns. (De acordo com a Programação de simultaneidade nos objetos imutáveis da JVM , é uma parte crítica da simultaneidade moderna).
A seguir, por que o AtomicReference é melhor que um objeto volátil para compartilhar esse valor compartilhado. Um exemplo de código simples mostrará a diferença.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Toda vez que você deseja modificar a sequência referenciada por esse campo volátil com base em seu valor atual, primeiro você precisa obter um bloqueio nesse objeto. Isso impede que algum outro encadeamento entre nesse meio tempo e altere o valor no meio da nova concatenação de strings. Então, quando o seu encadeamento continuar, você derruba o trabalho do outro encadeamento. Mas, honestamente, esse código funcionará, parece limpo e faria a maioria das pessoas feliz.
Pequeno problema. Está lento. Especialmente se houver muita contenção nesse objeto de bloqueio. Isso ocorre porque a maioria dos bloqueios exige uma chamada do sistema OS, e seu thread bloqueará e será alternado para fora da CPU para abrir caminho para outros processos.
A outra opção é usar um AtomicRefrence.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
Agora, por que isso é melhor? Honestamente, esse código é um pouco menos limpo do que antes. Mas há algo realmente importante que acontece no AtomicRefrence, que é comparar e trocar. É uma única instrução da CPU, não uma chamada do SO, que faz a troca acontecer. Essa é uma única instrução na CPU. E como não há bloqueios, não há mudança de contexto no caso em que o bloqueio é exercido, o que economiza ainda mais tempo!
O problema é que, para AtomicReferences, isso não usa uma chamada .equals (), mas uma comparação == para o valor esperado. Portanto, verifique se o esperado é o objeto real retornado de entrar no loop.
worked
para obter a mesma semântica.
Aqui está um caso de uso para AtomicReference:
Considere esta classe que atua como um intervalo numérico e usa variáveis AtmomicInteger individuais para manter limites numéricos inferiores e superiores.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
Ambos setLower e setUpper são sequências de verificação e ação, mas não usam bloqueio suficiente para torná-las atômicas. Se o intervalo de números for mantido (0, 10) e um thread chamar setLower (5) enquanto outro thread chamar setUpper (4), com algum tempo de azar, ambos passarão nas verificações nos setters e as duas modificações serão aplicadas. O resultado é que o intervalo agora mantém (5, 4) um estado inválido. Portanto, enquanto os AtomicIntegers subjacentes são seguros para threads, a classe composta não é. Isso pode ser corrigido usando um AtomicReference em vez de usar AtomicIntegers individuais para os limites superior e inferior.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
Você pode usar AtomicReference ao aplicar bloqueios otimistas. Você tem um objeto compartilhado e deseja alterá-lo de mais de 1 thread.
Como outro segmento pode ter modificado e / pode modificar entre essas duas etapas. Você precisa fazer isso em uma operação atômica. é aqui que o AtomicReference pode ajudar
Aqui está um caso de uso muito simples e não tem nada a ver com segurança de encadeamento.
Para compartilhar um objeto entre invocações lambda, AtomicReference
é uma opção :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Não estou dizendo que isso seja um bom design ou algo assim (é apenas um exemplo trivial), mas se você tiver o caso em que precisa compartilhar um objeto entre invocações lambda, essa AtomicReference
é uma opção.
De fato, você pode usar qualquer objeto que contenha uma referência, mesmo uma coleção que tenha apenas um item. No entanto, o AtomicReference é um ajuste perfeito.
Eu não vou falar muito. Meus respeitados colegas já deram sua contribuição valiosa. O código de execução completo no final deste blog deve remover qualquer confusão. Trata-se de um assento de filme que reserva um pequeno programa no cenário multiencadeado.
Alguns fatos elementares importantes são os seguintes. 1> Diferentes threads podem apenas conter, por exemplo, variáveis de membro estáticas no espaço de heap. 2> A leitura ou gravação volátil são completamente atômicas e serializadas / acontecem antes e são feitas apenas a partir da memória. Ao dizer isso, quero dizer que qualquer leitura seguirá a gravação anterior na memória. E qualquer gravação seguirá a leitura anterior da memória. Portanto, qualquer encadeamento que trabalhe com um volátil sempre verá o valor mais atualizado. AtomicReference usa essa propriedade de volátil.
A seguir, estão alguns dos códigos-fonte do AtomicReference. AtomicReference refere-se a uma referência de objeto. Essa referência é uma variável de membro volátil na instância AtomicReference, como abaixo.
private volatile V value;
get () simplesmente retorna o valor mais recente da variável (como os voláteis fazem da maneira "acontece antes").
public final V get()
A seguir, é apresentado o método mais importante do AtomicReference.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
O método compareAndSet (expect, update) chama o método compareAndSwapObject () da classe insegura de Java. Essa chamada de método inseguro chama a chamada nativa, que chama uma única instrução para o processador. "expect" e "update" referenciam cada objeto.
Se e somente se a variável de membro da instância AtomicReference "value" se referir ao mesmo objeto for referida por "expect", "update" será atribuída a essa variável de instância agora e "true" será retornado. Ou então, false é retornado. A coisa toda é feita atomicamente. Nenhum outro segmento pode interceptar no meio. Como esta é uma operação de processador único (mágica da arquitetura moderna do computador), geralmente é mais rápida do que usar um bloco sincronizado. Mas lembre-se de que quando várias variáveis precisam ser atualizadas atomicamente, o AtomicReference não ajuda.
Gostaria de adicionar um código de execução completo, que pode ser executado no eclipse. Isso esclareceria muitas confusões. Aqui 22 usuários (threads MyTh) estão tentando reservar 20 lugares. A seguir está o trecho de código seguido pelo código completo.
Snippet de código em que 22 usuários estão tentando reservar 20 vagas.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
A seguir, é apresentado o código completo.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
Quando usamos AtomicReference?
AtomicReference é uma maneira flexível de atualizar o valor da variável atomicamente sem o uso de sincronização.
AtomicReference
suporte a programação segura de thread sem bloqueio em variáveis únicas.
Existem várias maneiras de alcançar a segurança do thread com API simultânea de alto nível . Variáveis atômicas são uma das várias opções.
Lock
objetos oferecem suporte a idiomas de bloqueio que simplificam muitos aplicativos simultâneos.
Executors
defina uma API de alto nível para iniciar e gerenciar threads. As implementações de executores fornecidas pelo java.util.concurrent fornecem gerenciamento de conjunto de encadeamentos adequado para aplicativos de grande escala.
Coleções simultâneas facilitam o gerenciamento de grandes coleções de dados e podem reduzir bastante a necessidade de sincronização.
As variáveis atômicas possuem recursos que minimizam a sincronização e ajudam a evitar erros de consistência de memória.
Forneça um exemplo simples em que AtomicReference deve ser usado.
Código de exemplo com AtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
É necessário criar objetos em todos os programas multithread?
Você não precisa usar AtomicReference
em todos os programas multiencadeados.
Se você deseja proteger uma única variável, use AtomicReference
. Se você deseja proteger um bloco de código, use outras construções como Lock
/ synchronized
etc.
Outro exemplo simples é fazer uma modificação de thread seguro em um objeto de sessão.
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
Fonte: http://www.ibm.com/developerworks/library/j-jtp09238/index.html