Deve getters/setters de tipos primitivos ser bloqueado com ReadWriteLock em um multithreading aplicação?
-
14-11-2019 - |
Pergunta
Eu tenho uma classe Java que é usado em um multithreading aplicação.Acesso simultâneo é muito provável.Simultâneas múltiplas operações de leitura não deve bloquear então, eu estou usando um ReadWrite de bloqueio.
class Example {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int i;
private boolean b;
public int getI() {
lock.readLock().lock();
final int tmp = i;
lock.readLock().unlock(),
return tmp;
}
public void setI( final int i ) {
lock.writeLock().lock();
this.i = i;
lock.writeLock().unlock();
}
public boolean getB() {
lock.readLock().lock();
final boolean tmp = b;
lock.readLock().unlock(),
return tmp;
}
public void setB( final boolean b ) {
lock.writeLock().lock();
this.b = b;
lock.writeLock().unlock();
}
}
Para simplificar, eu deixei de fora a try...finally
os blocos ao redor os bloqueios neste exemplo.
Eu estou querendo saber se é necessário (ou, digamos, recomendadas) para bloquear/sincronizar getters e setters de tipos primitivos?Eu sei que atribuir e operações de devolução em Java são atômica.No entanto, ao usar esses bloqueios não ter certeza de que cada acessor obtém o valor mais recente (igual ao utilizar volatile
)?
O que se as primitivas foram double
ou long
?
Solução
Isso depende.
Note, no entanto, que, normalmente, você precisa para sincronizar as operações em mais de granulação grossa nível, por exemplo, este:
Example e = ...;
synchronized (e) {
e.setI(e.getI() + 10);
}
Para tais situações, seus bloqueios internos são redundantes.Portanto, talvez seria melhor aplicar a sincronização externa onde você usar esses objetos, em vez de sincronização interna.
Outras dicas
você tem algo como AtomicInteger em java, que funciona bem com o Aplicativo Multithread.
Pergunte a si mesmo se esta classe pode ser implementada como uma classe imutável.Será mais fácil para se trabalhar, e vai ser inerentemente thread-safe.Você não terá de se preocupar com a concorrência, bloqueios, sincronização, etc.
Exemplo de uma classe imutável:
final class Example {
private final int i;
private final boolean b;
public Example(int i, boolean b){
this.i = i ;
this.b = b;
}
public int getI() {
return i;
}
public boolean getB() {
return b;
}
}
Gostaria de projetar seu aplicativo para que você não vai ter acesso simultâneo a um tipo de dados raw como este.Adicionar como baixo nível de fecho é susceptível de diminuir a sua aplicação tanto é que não vale a pena multi-threading o seu aplicativo.
exemplo:Digamos que você tenha uma versão de 32 núcleo do sistema que balança perfeitamente e é executado 32x mais rápido do que em 1 de núcleo.No entanto, um campo de acesso sem fecho leva 1 ns, com fecho leva 1-nos (1000 ns) então, no final de sua aplicação pode demorar ~30x mais lento.(1000 mais lentos / mais rápido de 32) Se você tiver apenas 4 núcleos, pode ser centenas de vezes mais lento, derrotando o propósito de ter multi-threads em primeiro lugar.IMHO.
Não é necessário bloquear/sincronizar getters e setters de tipos primitivos - marcá-los como volátil é suficiente na maioria dos casos (para além do duplo e por muito tempo como você mencionou)
Como um dos posts anteriores menciona, você precisa estar ciente de leitura e atualização de sequências, algo como incrementI(int num), que provavelmente vai se chamar getI() e setI() - neste caso, você poderia adicionar um 'sincronizado incrementI(int num)" método para o seu Exemplo de classe.Fecho, em seguida, é feito em um nível mais alto, reduzindo a necessidade de separar ler e escrever bloqueios e é OO do ambiente de dados e o comportamento de ficar juntos.Esta técnica é ainda mais útil se você está a ler/atualizar vários campos de uma só vez.
Mas se você estiver simplesmente, leitura/escrita/atualização de um campo de cada vez, em seguida, o AtomicXX classes são mais apropriados
Você não deve usar o bloqueio de tipos primitivos, de Seqüência de caracteres (eles são imutáveis) e thread-safe tipos (como coleções de "concorrentes" pacote).