Esta é uma pesquisa de tipos sobre problemas de simultaneidade comuns em Java. Um exemplo pode ser o impasse clássico ou a condição de corrida ou talvez os erros de segmentação EDT no Swing. Estou interessado tanto em uma variedade de possíveis problemas, mas também em quais são os problemas mais comuns. Portanto, deixe uma resposta específica de um bug de concorrência Java por comentário e vote se você encontrar uma que encontrou.
java
multithreading
concurrency
Alex Miller
fonte
fonte
Respostas:
O problema de concorrência mais comum que eu já vi, não é perceber que um campo gravado por um thread não tem a garantia de ser visto por outro thread. Uma aplicação comum disso:
Enquanto parada não é volátil , ou
setStop
erun
não são sincronizados isso não é garantido que funcione. Esse erro é especialmente diabólico, pois em 99,999% não importa na prática, pois o segmento do leitor verá a mudança - mas não sabemos quanto tempo ele viu.fonte
Meu problema de concorrência mais doloroso nº 1 já ocorreu quando duas bibliotecas de código aberto diferentes fizeram algo parecido com isto:
À primeira vista, isso parece um exemplo de sincronização bastante trivial. Contudo; como Strings são internadas em Java, a string literal
"LOCK"
acaba sendo a mesma instância dejava.lang.String
(mesmo que sejam declaradas de maneira completamente díspar uma da outra). O resultado é obviamente ruim.fonte
Um problema clássico é alterar o objeto no qual você está sincronizando enquanto sincroniza:
Outros threads simultâneos são sincronizados em um objeto diferente e esse bloco não fornece a exclusão mútua que você espera.
fonte
Um problema comum é usar classes como Calendar e SimpleDateFormat de vários threads (geralmente armazenando-os em cache em uma variável estática) sem sincronização. Essas classes não são seguras para threads, portanto, o acesso multiencadeado acabará causando problemas estranhos com estado inconsistente.
fonte
Bloqueio verificado duas vezes. Em geral.
O paradigma, no qual comecei a aprender os problemas de quando estava trabalhando na BEA, é que as pessoas verifiquem um singleton da seguinte maneira:
Isso nunca funciona, porque outro encadeamento pode ter entrado no bloco sincronizado e s_instance não é mais nulo. Portanto, a mudança natural é:
Isso também não funciona, porque o Java Memory Model não é compatível. Você precisa declarar s_instance como volátil para fazê-lo funcionar e, mesmo assim, ele funciona apenas no Java 5.
Pessoas que não estão familiarizadas com os meandros do Java Memory Model mexem nisso o tempo todo .
fonte
Não sincronizando corretamente os objetos retornados por
Collections.synchronizedXXX()
, especialmente durante a iteração ou várias operações:Isso está errado . Apesar de operações únicas
synchronized
, o estado do mapa entre a chamadacontains
eput
pode ser alterado por outro encadeamento. Deveria ser:Ou com uma
ConcurrentMap
implementação:fonte
Embora provavelmente não seja exatamente o que você está procurando, o problema mais frequente relacionado à concorrência que encontrei (provavelmente porque aparece no código de thread único normal) é um
java.util.ConcurrentModificationException
causada por coisas como:
fonte
Pode ser fácil pensar que as coleções sincronizadas lhe concedem mais proteção do que realmente oferecem e esqueça de manter o bloqueio entre as chamadas. Eu já vi esse erro algumas vezes:
Por exemplo, na segunda linha acima, os métodos
toArray()
esize()
são ambos thread-safe por si só, mas osize()
é avaliado separadamente dotoArray()
e o bloqueio na lista não é mantido entre essas duas chamadas.Se você executar esse código com outro thread removendo simultaneamente os itens da lista, mais cedo ou mais tarde você terminará com um novo
String[]
retorno maior que o necessário para conter todos os elementos da lista e ter valores nulos na cauda. É fácil pensar que, como as duas chamadas de método para a Lista ocorrem em uma única linha de código, é de alguma forma uma operação atômica, mas não é.fonte
O erro mais comum que vemos onde trabalho é que os programadores executam operações longas, como chamadas de servidor, no EDT, bloqueando a GUI por alguns segundos e deixando o aplicativo sem resposta.
fonte
Esquecendo de wait () (ou Condition.await ()) em um loop, verificando se a condição de espera é realmente verdadeira. Sem isso, você encontra bugs de ativações espúrias de wait (). O uso canônico deve ser:
fonte
Outro erro comum é o tratamento inadequado de exceções. Quando um encadeamento em segundo plano lança uma exceção, se você não o manipular corretamente, poderá não ver o rastreamento da pilha. Ou talvez sua tarefa em segundo plano pare de executar e nunca inicie novamente porque você não conseguiu lidar com a exceção.
fonte
Até que eu fiz uma aula com Brian Goetz Eu não tinha percebido que o não-sincronizada
getter
de um campo privado transformado através de um sincronizadosetter
é não garantido para devolver o valor atualizado. Somente quando uma variável é protegida pelo bloco sincronizado nas duas leituras e gravações , você obtém a garantia do valor mais recente da variável.fonte
Pensando que você está escrevendo código de thread único, mas usando estática mutável (incluindo singletons). Obviamente eles serão compartilhados entre os threads. Isso acontece surpreendentemente com frequência.
fonte
As chamadas de método arbitrário não devem ser feitas a partir de blocos sincronizados.
Dave Ray mencionou isso em sua primeira resposta e, de fato, também encontrei um impasse relacionado à invocação de métodos para ouvintes de um método sincronizado. Acho que a lição mais geral é que as chamadas de método não devem ser feitas "in the wild" de dentro de um bloco sincronizado - você não tem idéia se a chamada será de longa duração, resultará em impasse ou qualquer outra coisa.
Nesse caso, e geralmente em geral, a solução era reduzir o escopo do bloco sincronizado para proteger apenas uma seção privada crítica do código.
Além disso, como agora acessávamos a coleção de ouvintes fora de um bloco sincronizado, a alteramos para uma coleção de copiar na gravação. Ou poderíamos simplesmente fazer uma cópia defensiva da coleção. O ponto é que geralmente existem alternativas para acessar com segurança uma coleção de objetos desconhecidos.
fonte
O bug mais recente relacionado à concorrência que encontrei foi um objeto que, em seu construtor, criou um ExecutorService, mas quando o objeto não era mais referenciado, ele nunca havia desligado o ExecutorService. Assim, durante um período de semanas, milhares de threads vazaram, causando o travamento do sistema. (Tecnicamente, não travou, mas parou de funcionar corretamente, enquanto continuava sendo executado.)
Tecnicamente, suponho que esse não seja um problema de simultaneidade, mas é um problema relacionado ao uso das bibliotecas java.util.concurrency.
fonte
A sincronização desequilibrada, particularmente no Maps, parece ser um problema bastante comum. Muitas pessoas acreditam que a sincronização de put em um mapa (não um ConcurrentMap, mas digamos um HashMap) e não a sincronização de get é suficiente. No entanto, isso pode levar a um loop infinito durante o re-hash.
O mesmo problema (sincronização parcial) pode ocorrer em qualquer lugar em que você tenha compartilhado o estado com leituras e gravações.
fonte
Encontrei um problema de simultaneidade com os Servlets, quando há campos mutáveis que serão definidos por cada solicitação. Mas há apenas uma instância de servlet para todas as solicitações, portanto, isso funcionou perfeitamente em um ambiente de usuário único, mas quando mais de um usuário solicitou, ocorreram resultados imprevisíveis do servlet.
fonte
Não é exatamente um bug, mas o pior pecado é fornecer uma biblioteca que você pretende que outras pessoas usem, mas não declarar quais classes / métodos são seguros para threads e quais devem ser chamados apenas de um único thread, etc.
Mais pessoas devem usar as anotações de simultaneidade (por exemplo, @ThreadSafe, @GuardedBy etc) descritas no livro de Goetz.
fonte
Meu maior problema sempre foram os impasses, causados principalmente por ouvintes que são acionados com um bloqueio. Nesses casos, é realmente fácil obter o bloqueio invertido entre dois threads. No meu caso, entre uma simulação em execução em um thread e uma visualização da simulação em execução no thread da interface do usuário.
EDIT: Movida a segunda parte para separar a resposta.
fonte
Iniciar um encadeamento no construtor de uma classe é problemático. Se a classe for estendida, o encadeamento poderá ser iniciado antes da execução do construtor da subclasse .
fonte
Classes mutáveis em estruturas de dados compartilhadas
Quando isso acontece, o código é muito mais complexo que este exemplo simplificado. Replicar, encontrar e corrigir o erro é difícil. Talvez isso pudesse ser evitado se pudéssemos marcar certas classes como imutáveis e certas estruturas de dados como apenas mantendo objetos imutáveis.
fonte
A sincronização em uma literal ou constante de cadeia definida por uma literal de cadeia é (potencialmente) um problema, pois a literal de cadeia é internada e será compartilhada por qualquer outra pessoa na JVM usando a mesma literal de cadeia. Sei que esse problema surgiu em servidores de aplicativos e outros cenários de "contêiner".
Exemplo:
Nesse caso, qualquer pessoa que use a string "foo" para bloquear está compartilhando o mesmo bloqueio.
fonte
Acredito que no futuro o principal problema com Java será a (falta de) garantia de visibilidade para os construtores. Por exemplo, se você criar a seguinte classe
e depois é só ler a propriedade das MyClass um de outro segmento, MyClass.a poderia ser 0 ou 1, dependendo da implementação e o humor do JavaVM. Hoje, as chances de 'a' ser 1 são muito altas. Mas em futuras máquinas NUMA, isso pode ser diferente. Muitas pessoas não estão cientes disso e acreditam que não precisam se preocupar com o multiencadeamento durante a fase de inicialização.
fonte
O erro mais estúpido que cometo com frequência é esquecer de sincronizar antes de chamar notify () ou wait () em um objeto.
fonte
Usando um "novo Object ()" local como mutex.
Isso é inútil.
fonte
Outro problema comum de "simultaneidade" é usar código sincronizado quando não for necessário. Por exemplo, ainda vejo programadores usando
StringBuffer
ou mesmojava.util.Vector
(como variáveis locais do método).fonte
Vários objetos protegidos por bloqueio, mas geralmente são acessados sucessivamente. Ocorremos em alguns casos em que os bloqueios são obtidos por código diferente em diferentes ordens, resultando em conflito.
fonte
Não percebendo que a
this
classe interna não éthis
a classe externa. Normalmente, em uma classe interna anônima que implementaRunnable
. O problema principal é que, como a sincronização faz parte de todos osObject
s, não há efetivamente nenhuma verificação de tipo estático. Eu já vi isso pelo menos duas vezes na usenet, e também aparece na concorrência simultânea de Brian Goetz'z Java na prática.Os fechamentos de BGGA não sofrem com isso, pois não há
this
fechamento (fazthis
referência à classe externa). Se você usarthis
objetos não como bloqueios, ele contorna esse problema e outros.fonte
Uso de um objeto global, como uma variável estática para bloqueio.
Isso leva a um desempenho muito ruim por causa da contenção.
fonte
Honestamente? Antes do advento
java.util.concurrent
, o problema mais comum que eu encontrava rotineiramente era o que chamo de "debulha de threads": aplicativos que usam threads para simultaneidade, mas geram muitos deles e acabam debatendo.fonte