Sincronização vs Bloqueio

java.util.concurrent API java.util.concurrent fornece uma class chamada Lock , que basicamente serializaria o controle para acessar o recurso crítico. Dá método como park() e unpark() .

Podemos fazer coisas semelhantes se pudermos usar palavras synchronized chave synchronized e usando os methods wait() e notify() notifyAll() .

Eu estou querendo saber qual destes é melhor na prática e por quê?

Se você está simplesmente bloqueando um object, eu prefiro usar synchronized

Exemplo:

 Lock.acquire(); doSomethingNifty(); // Throws a NPE! Lock.release(); // Oh noes, we never release the lock! 

Você tem que explicitamente try{} finally{} todos os lugares.

Considerando que, sincronizado, é super claro e impossível errar:

 synchronized(myObject) { doSomethingNifty(); } 

Dito isso, o Lock s pode ser mais útil para coisas mais complicadas, onde você não pode adquirir e liberar de maneira tão limpa. Eu sinceramente preferiria evitar o uso do Lock CyclicBarrier , e apenas usar um controle de concorrência mais sofisticado, como um CyclicBarrier ou um LinkedBlockingQueue , se eles atenderem às suas necessidades.

Eu nunca tive um motivo para usar wait() ou notify() mas pode haver alguns bons.

Eu estou querendo saber qual destes é melhor na prática e por quê?

Eu descobri que Lock e Condition (e outras novas classs concurrent ) são apenas mais ferramentas para a checkbox de ferramentas. Eu poderia fazer quase tudo que eu precisava com meu velho martelo (a palavra synchronized chave synchronized ), mas era difícil usá-lo em algumas situações. Várias dessas situações embaraçosas se tornaram muito mais simples quando adicionei mais ferramentas à minha checkbox de ferramentas: um martelo de borracha, um martelo de bola, um prybar e alguns pregos. No entanto , meu velho martelo ainda vê sua plot de uso.

Eu não acho que um é realmente “melhor” do que o outro, mas cada um é um melhor ajuste para diferentes problemas. Em suma, o modelo simples e a natureza orientada ao escopo do synchronized ajudam a proteger-me de erros no meu código, mas essas mesmas vantagens às vezes são obstáculos em cenários mais complexos. É estes cenários mais complexos que o pacote concorrente foi criado para ajudar a resolver. Mas o uso dessas construções de nível superior requer um gerenciamento mais explícito e cuidadoso no código.

===

Eu acho que o JavaDoc faz um bom trabalho ao descrever a distinção entre Lock e synchronized (a ênfase é minha):

As implementações de bloqueio fornecem operações de bloqueio mais extensas do que as que podem ser obtidas usando methods e instruções sincronizados. Eles permitem uma estruturação mais flexível , podem ter propriedades bem diferentes e podem suportar vários objects de condição associados .

O uso de methods ou instruções sincronizadas fornece access ao bloqueio de monitor implícito associado a cada object, mas força a aquisição e liberação de bloqueio a ocorrer de maneira estruturada em bloco : quando vários bloqueios são adquiridos, eles devem ser liberados na ordem oposta e todos os bloqueios devem ser liberados no mesmo escopo léxico em que foram adquiridos .

Embora o mecanismo de escopo para methods e instruções sincronizados facilite muito a programação com bloqueios do monitor e ajude a evitar muitos erros comuns de programação envolvendo bloqueios, há ocasiões em que é necessário trabalhar com bloqueios de maneira mais flexível. Por exemplo, * * alguns algoritmos * para atravessar estruturas de dados acessadas concorrentemente requerem o uso de “mão sobre mão” ou “travamento de cadeia” : você adquire o bloqueio do nó A, depois o nó B, solte A e adquira C, depois solte B e adquira D e assim por diante. As implementações da interface de bloqueio permitem o uso de tais técnicas, permitindo que um bloqueio seja adquirido e liberado em diferentes escopos e permitindo que vários bloqueios sejam adquiridos e liberados em qualquer ordem .

Com esse aumento de flexibilidade, surge uma responsabilidade adicional . A ausência de bloqueio estruturado em bloco remove a liberação automática de bloqueios que ocorre com methods e instruções sincronizados. Na maioria dos casos, o idioma a seguir deve ser usado:

Quando o bloqueio e o desbloqueio ocorrem em escopos diferentes , é necessário tomar cuidado para garantir que todo o código que é executado enquanto o bloqueio é mantido protegido por try-finally ou try-catch para garantir que o bloqueio seja liberado quando necessário.

As implementações de bloqueio fornecem funcionalidade adicional sobre o uso de methods e instruções sincronizados, fornecendo uma tentativa sem bloqueio para adquirir um bloqueio (tryLock ()), uma tentativa de adquirir o bloqueio que pode ser interrompido (lockInterruptibly () e uma tentativa de adquirir o bloqueio que pode expirar (tryLock (long, TimeUnit)).

Você pode obter tudo o que os utilitários em java.util.concurrent fazem com os primitivos de baixo nível, como synchronized , volatile ou wait / notify

No entanto, a simultaneidade é complicada, e a maioria das pessoas entende pelo menos algumas partes, tornando seu código incorreto ou ineficiente (ou ambos).

A API simultânea fornece uma abordagem de nível superior, que é mais fácil (e, portanto, mais segura) de ser usada. Em suma, você não precisa usar synchronized, volatile, wait, notify diretamente mais.

A própria class Lock está no lado de baixo nível desta checkbox de ferramentas, você pode nem precisar usá-la diretamente (você pode usar Queues e semáforos e outras coisas, etc, na maior parte do tempo).

Existem 4 fatores principais em por que você iria querer usar synchronized ou java.util.concurrent.Lock .

Nota: O bloqueio sincronizado é o que quero dizer quando digo bloqueio intrínseco.

  1. Quando o Java 5 foi lançado com o ReentrantLocks, eles provaram ter uma diferença de taxa de transferência bastante notável do que o bloqueio intrínseco. Se você está procurando um mecanismo de travamento mais rápido e está rodando 1.5, considere o jucReentrantLock. O bloqueio intrínseco do Java 6 é agora comparável.

  2. O jucLock possui mecanismos diferentes para bloqueio. Bloqueio de interrupção – tente bloquear até que o encadeamento de bloqueio seja interrompido; bloqueio temporizado – tente bloquear por um determinado período de tempo e desista caso não tenha êxito; tryLock – tentativa de bloqueio, se algum outro segmento está segurando o bloqueio desistir. Tudo isso está incluído além do bloqueio simples. Bloqueio intrínseco oferece apenas bloqueio simples

  3. Estilo. Se ambos 1 e 2 não se enquadrarem em categorias do que você está preocupado com a maioria das pessoas, incluindo eu mesmo, verificaria que o semenatismo de bloqueio intrínseco é mais fácil de ler e menos detalhado do que o bloqueio de jucLock.
  4. Múltiplas Condições. Um object que você bloqueia só pode ser notificado e aguardado por um único caso. O método newCondition do Lock permite que um único Lock tenha vários motivos para aguardar ou sinalizar. Eu ainda tenho que realmente precisar dessa funcionalidade na prática, mas é um bom recurso para quem precisa.

A principal diferença é a equidade, em outras palavras, as solicitações são manipuladas pelo FIFO ou podem existir barganhas? A synchronization no nível do método garante a alocação justa ou FIFO do bloqueio. Usando

 synchronized(foo) { } 

ou

 lock.acquire(); .....lock.release(); 

não garante justiça.

Se você tiver muita contenção para o bloqueio, poderá encontrar barreiras com facilidade quando solicitações mais recentes receberem o bloqueio e as solicitações mais antigas ficarem presas. Já vi casos em que 200 threads chegam em pouco tempo para uma trava e a segunda para chegar é processada por último. Isso é ok para algumas aplicações, mas para outros é mortal.

Veja o livro “Java Concurrency In Practice” de Brian Goetz, seção 13.3 para uma discussão completa sobre este tópico.

O livro “Java Concurrency In Practice” de Brian Goetz, seção 13.3: “… Como o ReentrantLock padrão, o bloqueio intrínseco não oferece garantias determinísticas de justiça, mas as garantias statistics de estabilidade da maioria das implementações de bloqueio são boas o suficiente para quase todas as situações …”

Eu gostaria de acrescentar mais algumas coisas sobre a resposta do Bert F.

Locks suportam vários methods para um controle de trava mais refinado, que são mais expressivos do que os monitores implícitos (bloqueios synchronized )

Um bloqueio fornece access exclusivo a um recurso compartilhado: apenas um thread por vez pode adquirir o bloqueio e todo o access ao recurso compartilhado exige que o bloqueio seja adquirido primeiro. No entanto, alguns bloqueios podem permitir o access simultâneo a um recurso compartilhado, como o bloqueio de leitura de um ReadWriteLock.

Vantagens do bloqueio sobre a synchronization da página de documentação

  1. O uso de methods ou instruções sincronizados fornece access ao bloqueio de monitor implícito associado a cada object, mas força a aquisição e liberação de bloqueio a ocorrer de maneira estruturada em bloco

  2. As implementações de bloqueio fornecem funcionalidade adicional sobre o uso de methods e instruções sincronizados, fornecendo uma tentativa sem bloqueio para adquirir um lock (tryLock()) , uma tentativa de adquirir o bloqueio que pode ser interrompido ( lockInterruptibly() e uma tentativa de adquirir o bloqueio que pode timeout (tryLock(long, TimeUnit)) .

  3. Uma class de bloqueio também pode fornecer comportamento e semântica que é bastante diferente do bloqueio de monitor implícito, como ordenação garantida, uso não-reentrante ou detecção de deadlock

ReentrantLock : Em termos simples, segundo meu entendimento, o ReentrantLock permite que um object entre novamente de uma seção crítica para outra seção crítica. Desde que você já tem bloqueio para entrar em uma seção crítica, você pode outra seção crítica no mesmo object usando o bloqueio atual.

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.

Além destes três ReentrantLocks, o java 8 fornece mais um bloqueio

StampedLock:

O Java 8 vem com um novo tipo de bloqueio chamado StampedLock, que também suporta bloqueios de leitura e gravação, como no exemplo acima. Em contraste com ReadWriteLock, os methods de bloqueio de um StampedLock retornam um selo representado por um valor longo.

Você pode usar esses carimbos para liberar um bloqueio ou para verificar se o bloqueio ainda é válido. Além disso, os bloqueios estampados suportam outro modo de bloqueio chamado bloqueio otimista.

Dê uma olhada neste artigo sobre o uso de diferentes tipos de bloqueios StampedLock e StampedLock .

O bloqueio torna a vida dos programadores mais fácil. Aqui estão algumas situações que podem ser alcançadas mais facilmente com o bloqueio.

  1. Bloqueie em um método e libere o bloqueio em outro método.
  2. Você tem dois encadeamentos trabalhando em duas partes diferentes de código, no entanto, o primeiro encadeamento depende da segunda encadernação para concluir uma parte do código antes de prosseguir (enquanto alguns outros encadeamentos também funcionam simultaneamente). Um bloqueio compartilhado pode resolver esse problema com bastante facilidade.
  3. Implementando monitores. Por exemplo, uma fila simples em que os methods put e get são executados a partir de vários threads diferentes. No entanto, você quer mais ou menos os mesmos methods um no outro, nem ambos colocar e obter methods podem se sobrepor. Nesse caso, uma fechadura privada torna a vida muito mais fácil.

Enquanto, o bloqueio e as condições são construídos no sincronizado. Então, certamente você pode conseguir o mesmo objective com isso. No entanto, isso pode dificultar sua vida e desviá-lo da resolução do problema real.

A principal diferença entre bloqueio e sincronizado é – com bloqueios, você pode liberar e adquirir os bloqueios em qualquer ordem. – com sincronizado, você pode liberar os bloqueios apenas na ordem em que foi adquirido.

Bloquear e sincronizar o bloco serve a mesma finalidade, mas depende do uso. Considere a parte abaixo

 void randomFunction(){ . . . synchronize(this){ //do some functionality } . . . synchronize(this) { // do some functionality } } // end of randomFunction 

No caso acima, se um thread entrar no bloco de synchronization, o outro bloco também será bloqueado. Se houver vários desses blocos de synchronization no mesmo object, todos os blocos serão bloqueados. Em tais situações, java.util.concurrent.Lock pode ser usado para impedir o bloqueio indesejado de blocos