Concordo com um dos comentários de John: Você deve sempre usar um dummy de bloqueio final ao acessar uma variável não final para evitar inconsistências no caso de alterações de referência da variável. Portanto, em qualquer caso e como primeira regra prática:
Regra # 1: Se um campo não for final, sempre use um modelo de bloqueio final (privado).
Razão # 1: você mantém o bloqueio e altera a referência da variável por conta própria. Outro thread esperando fora do bloqueio sincronizado poderá entrar no bloco protegido.
Razão # 2: você mantém o bloqueio e outro thread altera a referência da variável. O resultado é o mesmo: outro thread pode entrar no bloco protegido.
Mas, ao usar um boneco de bloqueio final, há outro problema : você pode obter dados errados, porque seu objeto não final só será sincronizado com a RAM ao chamar synchronize (objeto). Portanto, como uma segunda regra prática:
Regra # 2: Ao bloquear um objeto não final, você sempre precisa fazer as duas coisas: Usar um manequim de bloqueio final e o bloqueio do objeto não final para o bem da sincronização de RAM. (A única alternativa será declarar todos os campos do objeto como voláteis!)
Esses bloqueios também são chamados de "bloqueios aninhados". Observe que você deve chamá-los sempre na mesma ordem, caso contrário, você obterá um bloqueio morto :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
//do something with o...
}
}
}
}
Como você pode ver, escrevo os dois cadeados diretamente na mesma linha, porque eles sempre pertencem um ao outro. Assim, você pode até fazer 10 bloqueios de aninhamento:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
//entering the locked space
}
}
}
}
Observe que este código não será quebrado se você apenas adquirir um bloqueio interno como synchronized (LOCK3)
por outras threads. Mas ele vai quebrar se você chamar em outro tópico algo como este:
synchronized (LOCK4) {
synchronized (LOCK1) { //dead lock!
synchronized (LOCK3) {
synchronized (LOCK2) {
//will never enter here...
}
}
}
}
Há apenas uma solução alternativa para esses bloqueios aninhados ao lidar com campos não finais:
Regra # 2 - Alternativa: Declare todos os campos do objeto como voláteis. (Não vou falar aqui sobre as desvantagens de fazer isso, por exemplo, evitar qualquer armazenamento em caches de nível x, mesmo para leituras, também.)
Portanto, aioobe está certa: basta usar java.util.concurrent. Ou comece a entender tudo sobre sincronização e faça você mesmo com bloqueios aninhados. ;)
Para obter mais detalhes por que a sincronização em campos não finais quebras, dê uma olhada em meu caso de teste: https://stackoverflow.com/a/21460055/2012947
E para obter mais detalhes por que você precisa sincronizar tudo devido à RAM e aos caches, dê uma olhada aqui: https://stackoverflow.com/a/21409975/2012947
o
referido no momento em que o bloco sincronizado foi atingido. Se o objeto queo
se refere a alterações, outro thread pode vir e executar o bloco de código sincronizado.