Ultimamente se tornou inevitável desenvolver programas que funcionam de maneira concorrente, já que os processadores com multiplos cores já estão em todo lugar, até mesmo nos celulares. E ao mesmo tempo que é extremamente prazeroso ver o seu software muito mais rápido graças a concorrência, também se pode perder muitas noites de sono com os problemas causados por erros no desenvolvimento de software concorrente. Neste post eu pretendo falar de maneira breve dos três principais problemas que podem surgir quando se desenvolve software concorrente. São eles: Starvation, Deadlocks e Race Conditions.
Starvation
Starvation ocorre quando uma thread fica esperando uma resposta pra prosseguir e essa resposta nunca chega, logo, a thread permanece viva, ocupando recursos, mas não produzindo nada de útil para o software. Uma boa forma de tratar esse problema é sempre trabalhar com timeout e fluxo de exceção, para que caso a resposta não chegue em um determinado tempo seja seguido o fluxo de exceção e a thread possa finalizar o seu trabalho.
Deadlocks
Deadlock ocorre se duas ou mais threads ficam aguardando uma a outra para realizar alguma ação ou liberar algum recurso, por exemplo, a thread A ficar aguardando a thread B liberar algum recurso e B ficar aguardando A terminar alguma ação pra liberar esse recurso, logo, ambas vão permanecer eternamente esperando. Não existe uma solução simples pra resolver esse problema como o timeout para o problema de Starvation, mas se a aplicação não possuir locks explicitos e estados mutáveis então ela estará a salvo de deadlocks, então esse pode ser um bom caminho a se seguir.
Race Conditions
Race Condition ocorre se duas threads concorrem para usar o mesmo recurso ou dado. É um problema bem difícil de tratar pois o comportamento da aplicação se torna imprevisível e uma Race Condition pode ocorrer depois de meses do software em produção. Pra piorar, este problema não ocorre apenas quando duas threads tentam modificar o mesmo dado, ele pode correr quando uma thread tenta gravar um dado e a outra está tentando apenas ler este mesmo dado. Pra piorar ainda mais, existem casos aonde se o código não é otimizado com O JIT(Just In Time) Compiler a Race Condition não aparece, mas se o código é elegível ao JIT e é compilado então a Race Condition surge, basicamente porque a JVM passa a usar cache para acessar algumas variáveis e se outra thread muda o valor da variável, esse valor não é replicado para o cache. Neste caso citado a solução seria usar volatile e/ou synchronized.
Novamente não usar objetos mutáveis evitaria o problema de Race Conditions, por isso que imutabilidade tem sido um tema tão comentado ultimamente, e linguagens que pregam essa técnica tem ganhado cada vez mais força.
Para se aprofundar mais no assunto, inclusive com um código de exemplo que mostra uma Race Condition causada pelo JIT, recomendo a leitura do livro “Programming Concurrency on the JVM”.