2009-12-01

Usar locks explícitas, correctamente

Por vezes synchronized não serve e temos de usar locks explicitamente. No entanto, quando abandonamos a simplicidade de synchronized, também abandonamos as suas garantias de segurança e robustez.

Código frágil, aka vidro

Uma transformação ingénua de código com locks implícitas para locks explícitas seria desta forma:

// implícita
synchronized(obj) {
// região crítica
}

// explícita (não é seguro!)
Lock l; // a lock é inicializada algures (geralmente no constructor)

l.lock();
// região crítica
l.unlock();

No entanto, estas formas não são equivalentes. synchronized garante que a lock é libertada quando o bloco termina. Não interessa se ocorrem excepções ou se corre tudo bem, synchronized liberta sempre a lock. Não libertar uma lock é, obviamente, muito mau. Da próxima vez que uma thread tentar adquirir a lock, ocorre um deadlock.

Usando locks explícitas como no exemplo acima a lock só é libertada se a região crítica correr até ao fim sem problemas. Se for lançada uma excepção na região crítica, a lock fica por libertar. Quando fiz a cadeira de Sistemas Distribuídos não ouvi falar deste problema, e pelo que me disseram alguns colegas, este ano também não foi mencionado. Não percebo porquê.

Uma outra situação é quando existe um return na região crítica, que torna necessário criar uma variável local para manter o resultado e devolvê-lo no fim, ou então a lock nunca será libertada porque o método termina antes:

// implícita
synchronized(obj) {
// calcular resultado
return compute();
}

// explícita (não é seguro!)
l.lock(); {
// calcular resultado
return compute();
} l.unlock();

// explícita com temp
int result;
l.lock(); {
// calcular resultado
result = compute();
} l.unlock();
return result;

Código robusto

Para garantir que a lock é sempre libertada temos de rodear o código com try-finally. Toda a gente conhece try-catch mas, pela minha experiência, muita gente nem sequer sabe que existe finally, o que é uma pena. try-finally é muito mais útil e muito mais importante que try-catch. No entanto parece que quando se aprende Java existe um foco maior no catch. A culpa, mais uma vez, é do mau design das checked exceptions.

finally é uma parte dos blocos de tratamento de excepções (try) que corre sempre depois do bloco, independemente de terem ocorrido excepções, returns ou o bloco chegar ao fim.

A forma correcta de usar locks explícitas recorre a try-finally:

l.lock();
try {
// região crítica
}
finally {
l.unlock();
}

Não interessa se a região crítica falha com uma excepção, ou tem returns, ou corre com sucesso até ao fim. A lock será sempre libertada. No fundo isto é o mesmo que synchronized.

Se for necessário usar um bloco catch para tratar uma determinada excepção (ou para adicionar lixo por causa de checked exceptions) também o podemos fazer:

l.lock();
try {
// região crítica
}
catch (SomeException e) {
// fazer alguma coisa
}
finally {
l.unlock();
}

Sempre

Quando usarem locks explícitas, usem sempre try-finally. Mesmo que a região crítica não tenha returns ou excepções:

  • não é necessário analisar a região crítica linha-a-linha para verificar se podem existem problemas;
  • a região crítica pode ser facilmente alterado sem introduzir este tipo de falhas;
  • nem sempre é fácil (ou possível) de garantir que não há excepções (quando se chamam métodos de classes que não são final, ou RuntimeExceptions)

1 commentários:

Anónimo disse...

I got this site from my pal who informed me concerning this website
and at the moment this time I am visiting this web site and
reading very informative posts at this place.


Check out my homepage; home cellulite treatment

Enviar um comentário