Por que usar um ReentrantLock se alguém pode usar sincronizado (isso)?

Eu estou tentando entender o que torna o bloqueio em simultaneidade tão importante se alguém pode usar synchronized (this) . No código fictício abaixo, posso fazer o seguinte:

  1. sincronizou todo o método ou sincronizou a área vulnerável (sincronizada (isto) {…})
  2. OU bloqueie a área de código vulnerável com um ReentrantLock.

Código:

  private final ReentrantLock lock = new ReentrantLock(); private static List ints; public Integer getResult(String name) { . . . lock.lock(); try { if (ints.size()==3) { ints=null; return -9; } for (int x=0; x>>>"+ints.get(x)); } } finally { lock.unlock(); } return random; } 

    Um ReentrantLock não é estruturado , ao contrário das construções synchronized – ou seja, você não precisa usar uma estrutura de bloco para bloquear e pode até manter um bloqueio entre os methods. Um exemplo:

     private ReentrantLock lock; public void foo() { ... lock.lock(); ... } public void bar() { ... lock.unlock(); ... } 

    Esse stream é impossível de representar por meio de um único monitor em uma construção synchronized .


    Além disso, o ReentrantLock oferece suporte a bloqueio de pesquisa e esperas de bloqueio interrompíveis que suportam o tempo limite . ReentrantLock também oferece suporte a políticas de justiça configuráveis , permitindo agendamento de threads mais flexível.

    O construtor para essa class aceita um parâmetro de igualdade opcional. Quando set true , sob contenção, bloqueia a concessão de access ao thread de espera mais longa. Caso contrário, esse bloqueio não garante nenhuma ordem de access específica. Os programas que usam bloqueios justos acessados ​​por vários threads podem exibir um rendimento geral menor (ou seja, são mais lentos; geralmente muito mais lentos) do que aqueles que usam a configuração padrão, mas possuem variações menores nos horários para obter bloqueios e garantir falta de inanição. Note, no entanto, que a justiça das fechaduras não garante a equidade do agendamento de threads. Assim, um dos muitos encadeamentos que usam um bloqueio justo pode obtê-lo várias vezes em sucessão, enquanto outros encadeamentos ativos não estão progredindo e não mantendo o bloqueio no momento. Observe também que o método tryLock indeterminada não respeita a configuração de justiça. Ele será bem-sucedido se o bloqueio estiver disponível, mesmo se outros segmentos estiverem aguardando.


    ReentrantLock também pode ser mais escalável , tendo um desempenho muito melhor sob uma contenção maior. Você pode ler mais sobre isso aqui .

    Esta alegação foi contestada, no entanto; veja o seguinte comentário:

    No teste de bloqueio reentrante, um novo bloqueio é criado a cada vez, portanto, não há bloqueio exclusivo e os dados resultantes são inválidos. Além disso, o link IBM não oferece código-fonte para o benchmark subjacente, de modo que é impossível caracterizar se o teste foi conduzido corretamente.


    Quando você deve usar o ReentrantLock s? De acordo com esse artigo do developerWorks …

    A resposta é bastante simples – use-a quando realmente precisar de algo que ela forneça que não synchronized , como esperas de bloqueio temporizadas, esperas de bloqueio interrompíveis, bloqueios não estruturados em block, variables ​​de condição múltiplas ou pesquisa de bloqueio. ReentrantLock também tem benefícios de escalabilidade, e você deve usá-lo se realmente tiver uma situação que exiba alta contenção, mas lembre-se de que a grande maioria dos blocos synchronized quase nunca exibe qualquer contenção, sem falar em alta contenção. Eu aconselharia o desenvolvimento com synchronization até que a synchronization tenha se mostrado inadequada, em vez de simplesmente assumir que “o desempenho será melhor” se você usar o ReentrantLock . Lembre-se, estas são ferramentas avançadas para usuários avançados. (E os usuários realmente avançados tendem a preferir as ferramentas mais simples que podem encontrar até estarem convencidos de que as ferramentas simples são inadequadas.) Como sempre, acerte-se primeiro e depois se preocupe se precisa ou não torná-lo mais rápido.

    Uma trava de reentrância permitirá que o porta-trava insira blocos de código mesmo depois de ter obtido a trava inserindo outros blocos de código. Um bloqueio não reentrante teria o bloco de bloqueio bloqueado em si mesmo, pois ele teria que liberar o bloqueio obtido de outro bloco de código para obter novamente o mesmo bloqueio para inserir o bloqueio nested que requer o bloco de código

      public synchronized void functionOne() { // do something functionTwo(); // do something else // redundant, but permitted... synchronized(this) { // do more stuff } } public synchronized void functionTwo() { // do even more stuff! } 

    Recursos estendidos de bloqueio reentrante incluem: –

    1. A capacidade de ter mais de uma condição variável por monitor. Monitores que usam a palavra-chave sincronizada podem ter apenas um. Isso significa que os bloqueios reentrantes suportam mais de uma fila wait () / notify ().
    2. A capacidade de tornar o bloqueio “justo”. “[fair] bloqueia a concessão de access ao thread de espera mais longa. Caso contrário, esse bloqueio não garante nenhuma ordem de access específica.” Blocos sincronizados são injustos.
    3. A capacidade de verificar se o bloqueio está sendo mantido.
    4. A capacidade de obter a lista de encadeamentos que estão aguardando no bloqueio.

    Desvantagens de bloqueios reentrantes são: –

    Precisa adicionar uma declaração de importação. Precisa quebrar as aquisições de bloqueio em um bloco try / finally. Isso torna mais feio do que a palavra-chave sincronizada. A palavra-chave sincronizada pode ser colocada nas definições do método, o que evita a necessidade de um bloco que reduz o aninhamento.

    Quando usar :-

    1. ReentrantLock pode estar mais apto a ser usado se você precisar implementar um thread que atravesse uma linked list, bloqueando o próximo nó e desbloqueando o nó atual.
    2. A palavra-chave sincronizada é adequada em situações como o aumento da trava, fornece rotação adaptativa, travamento polarizado e o potencial de elisão de travamento via análise de escape. Essas otimizações não são implementadas atualmente no ReentrantLock.

    Para mais informações .

    ReentrantReadWriteLock é um bloqueio especializado enquanto synchronized(this) é um bloqueio de propósito geral. Eles são semelhantes, mas não exatamente o mesmo.

    Você está certo em que você poderia usar synchronized(this) vez de ReentrantReadWriteLock mas o oposto não é sempre verdadeiro.

    Se você quiser entender melhor o que faz com que o ReentrantReadWriteLock especial procure algumas informações sobre a synchronization de threads do produtor / consumidor.

    Em geral, você pode lembrar que a synchronization de todo o método e a synchronization de uso geral (usando a palavra synchronized chave synchronized ) podem ser usadas na maioria das aplicações sem pensar muito sobre a semântica da synchronization, mas se você precisar extrair o desempenho de seu código para explorar outros mecanismos de synchronization mais refinados ou de finalidade especial.

    By the way, usando synchronized(this ) – e, em geral, o bloqueio usando uma instância de class pública – pode ser problemático, pois abre o seu código para possíveis fechaduras, porque alguém não pode, intencionalmente, tentar bloquear o seu object em outro lugar no programa.

    Da página de documentação do oracle sobre ReentrantLock :

    Uma exclusão mútua reentrante Bloqueie com o mesmo comportamento básico e semântica que o bloqueio de monitor implícito acessado usando methods e instruções sincronizados, mas com resources estendidos.

    1. Um ReentrantLock pertence ao último bloqueio com sucesso, mas ainda não o desbloqueou. Um encadeamento que chama o bloqueio retornará, adquirindo o bloqueio com êxito, quando o bloqueio não pertence a outro encadeamento. O método retornará imediatamente se o encadeamento atual já possuir o bloqueio.

    2. O construtor para essa class aceita um parâmetro de igualdade opcional. Quando set true, sob contenção, bloqueia a concessão de access ao thread de espera mais longa . Caso contrário, esse bloqueio não garante nenhuma ordem de access específica.

    Recursos-chave do ReentrantLock conforme este artigo

    1. Capacidade de travar de forma interrompida.
    2. Capacidade de tempo limite enquanto aguarda o bloqueio.
    3. Poder para criar um bloqueio justo.
    4. API para obter lista de thread em espera para bloqueio.
    5. Flexibilidade para tentar bloquear sem bloquear.

    Você pode usar ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock para adquirir ainda mais controle sobre bloqueio granular em operações de leitura e gravação.

    Dê uma olhada neste artigo de Benjamen sobre o uso de diferentes tipos de ReentrantLocks

    Você pode usar bloqueios reentrantes com uma política de justiça ou tempo limite para evitar a privação de threads. Você pode aplicar uma política de justiça de thread. isso ajudará a evitar que uma thread espere para sempre para acessar seus resources.

     private final ReentrantLock lock = new ReentrantLock(true); //the param true turns on the fairness policy. 

    A “política de justiça” seleciona o próximo thread executável a ser executado. É baseado em prioridade, tempo desde a última execução, blá blá

    também, Synchronize pode bloquear indefinidamente se não puder escaping do bloco. Reentrantlock pode ter tempo limite definido.