Multi-threading: objetos sendo definido como nulo enquanto usá-los
-
21-08-2019 - |
Pergunta
Eu tenho um pequeno aplicativo que tem uma rosca Render. Tudo esta discussão faz é chamar meus objetos em sua posição atual.
Eu tenho algum código como:
public void render()
{
// ... rendering various objects
if (mouseBall != null) mouseBall.draw()
}
Então eu também tenho alguns manipulador rato que cria e define mouseBall a uma nova bola quando o usuário clica no mouse. O usuário pode, em seguida, arraste o em torno do mouse ea bola vai seguir onde o mouse vai. Quando o usuário libera a bola Eu tenho um outro evento de mouse que conjuntos mouseBall = null.
O problema é que a minha tornar circuito está correndo rápido o suficiente para que em momentos aleatórios do condicional (mouseBall! = Null) retornará verdadeiro, mas em que fração de segundo depois desse ponto o usuário vai deixar de ir ao rato e eu vou obter uma exceção nullpointer para tentar .draw () em um objeto nulo.
O que é a solução para um problema como este?
Solução
As mentiras problema no fato de que você está acessando mouseBall
duas vezes, uma vez para verificar se não é null
e outro para chamar uma função nele. Você pode evitar esse problema usando um temporária como este:
public void render()
{
// ... rendering various objects
tmpBall = mouseBall;
if (tmpBall != null) tmpBall.draw();
}
Outras dicas
Você tem que sincronizar as declarações se e desenhar de forma que eles são garantidos para ser executado como uma seqüência atômica. Em java, isso seria feito assim:
public void render()
{
// ... rendering various objects
synchronized(this) {
if (mouseBall != null) mouseBall .draw();
}
}
Eu sei que você já aceitou outras respostas, mas uma terceira opção seria a utilização de classe AtomicReference do pacote java.util.concurrent.atomic. Isso proporciona a recuperação, atualização e operações comparar que atuam atomicamente sem ser necessário qualquer código de apoio. Assim, no seu exemplo:
public void render()
{
AtomicReference<MouseBallClass> mouseBall = ...;
// ... rendering various objects
MouseBall tmpBall = mouseBall.get();
if (tmpBall != null) tmpBall.draw();
}
Este é muito parecido com a solução de Greg, e conceitualmente elas são semelhantes em que, nos bastidores tanto a volatilidade do uso para garantir frescura dos valores, e tirar uma cópia temporária, a fim de aplicar uma condicional antes de usar o valor.
Por conseguinte, o exemplo exato usado aqui não é que bom para mostrar o poder dos AtomicReferences. Considere vez que o seu outro segmento irá atualizar o mouseball vairable somente se ele já foi nula - uma expressão útil para vários blocos de estilo de inicialização de código. Neste caso, ele normalmente seria imprescindível a utilização de sincronização, para assegurar que se verificou e constatou a bola foi nula, seria ainda ser nulo quando tentou defini-lo (caso contrário, você está de volta nos reinos do seu problema original). No entanto, com o AtomicReference você pode simplesmente dizer:
mouseBall.compareAndSet(null, possibleNewBall);
porque esta é uma operação atômica, por isso, se uma thread "vê" o valor como nulo que também irá configurá-lo para a referência possibleNewBall antes de quaisquer outros tópicos ter a chance de lê-lo.
Outra agradável idioma com referências atômicas é se você estiver configurando incondicionalmente alguma coisa, mas necessidade de realizar algum tipo de limpeza com o valor antigo. Caso em que você pode dizer:
MouseBall oldBall = mouseBall.getAndSet(newMouseBall);
// Cleanup code using oldBall
AtomicIntegers tem esses benefícios e muito mais; o método getAndIncrement () é maravilhoso para contadores globalmente partilhados, como você pode garantir cada chamada para ele retornará um valor distinto, independentemente da intercalação de threads. Segmento de segurança com um mínimo de barulho.