Adicionando ao AtomicInteger no ConcurrentHashMap - java, multithreading, simultaneidade, java.util.concurrent, concurrenthashmap

Eu tenho o seguinte definido

private ConcurrentMap<Integer, AtomicInteger>  = new ConcurrentHashMap<Integer, AtomicInteger>();

private void add() {
staffValues.replace(100, staffValues.get(100), new AtomicInteger(staffValues.get(100).addAndGet(200)));
}

After testing, the values I am getting are not esperado, e eu acho que há uma condição de corrida aqui. Alguém sabe se isso seria considerado thread-safe, envolvendo a chamada get na função de substituição?

Respostas:

2 para resposta № 1

Existem alguns problemas com o seu código. O maior é que você está ignorando o valor de retorno de ConcurrentHashMap.replace: se a substituição não acontecer (devido a outro thread ter feito uma substituição em paralelo), simplesmente continue Até parece aconteceu. Esta é a principal razão pela qual você está obtendo resultados errados.

Eu também acho que é um erro de projeto para mudar um AtomicInteger e, em seguida, substituí-lo imediatamente por um diferente AtomicInteger; mesmo que você consiga isso funcionar, simplesmente não há razão para isso.

Por fim, eu não acho que você deveria ligar staffValues.get(100) duas vezes. Eu não acho que isso causa um bug no código atual - sua exatidão depende apenas da segunda chamada retornando um resultado "mais recente" do que o primeiro, o que eu acho é realmente garantido por ConcurrentHashMap - mas é frágil, sutil e confuso. Em geral, quando você liga ConcurrentHashMap.replace, seu terceiro argumento deve ser algo que você calculou usando o segundo.

No geral, você pode simplificar seu código ou não usando AtomicInteger:

private ConcurrentMap<Integer, Integer> staffValues = new ConcurrentHashMap<>();

private void add() {
final Integer prevValue = staffValues.get(100);
staffValues.replace(100, prevValue, prevValue + 200);
}

ou não usando replace (e talvez nem mesmo ConcurrentMap, dependendo de como mais você está tocando este mapa:

private Map<Integer, AtomicInteger> staffValues = new HashMap<>();

private void add() {
staffValues.get(100).addAndGet(200);
}

1 para resposta № 2

Uma boa maneira de lidar com situações como esta é usar o computeIfAbsent método (não o compute método que @ the8472 recomenda)

o computeIfAbsent aceita 2 argumentos, a chave e um Function<K, V> que só será chamado se o valor existente estiver faltando. Como um AtomicInteger é thread-safe para incrementar a partir de vários threads, você pode usá-lo da seguinte maneira:

staffValues.computeIfAbsent(100, k -> new AtomicInteger(0)).addAndGet(200);

0 para resposta № 3

Você não precisa usar replace(). AtomicInteger é um valor mutável que não precisa ser substituído sempre que você quiser incrementá-lo. de fato addAndGet já incrementa no lugar.

Em vez disso, use compute para colocar um valor padrão (presumivelmente 0) no mapa quando nenhum estiver presente e, caso contrário, obter o valor preexistente e incrementá-lo.

Se, por outro lado, você quiser usar valores imutáveis, coloque Integer instâncias em vez de AtomicInteger no mapa e atualizá-los com as operações atômicas computar / substituir / mesclar.