Lock e Condition
Em Java, a sincronização padrão é baseada na palavra-chave synchronized e em objetos monitor incorporados. No entanto, em alguns casos, o synchronized pode não ser suficiente, especialmente quando é necessária maior flexibilidade no gerenciamento de threads.
Descrição Geral
As interfaces Lock e Condition, introduzidas no pacote java.util.concurrent.locks, oferecem recursos avançados de gerenciamento de threads.
Nesta imagem, é possível observar que a primeira thread adquire o lock utilizando o método lock(), e nesse momento, outra thread não pode adquirir o mesmo lock. Assim que todo o código dentro do lock for executado, será chamado o método unlock() para liberar o lock. Somente após isso a segunda thread poderá adquirir o lock.
Diferença
A diferença entre essas duas interfaces é que as implementações de Lock são uma alternativa de alto nível ao bloco synchronized, e as implementações da interface Condition são uma alternativa aos métodos notify()/wait(). Ambas as interfaces fazem parte do pacote java.util.concurrent.locks.
Exemplos do Mundo Real
Imagine o gerenciamento de uma fila para inscrição em um evento. Para evitar excesso de inscrições e garantir a alocação adequada de assentos, é necessário utilizar mecanismos de bloqueio e condições para manter o fluxo de novas inscrições aguardando até que uma vaga esteja disponível.
Classe ReentrantLock
A classe ReentrantLock do pacote java.util.concurrent.locks é uma implementação da interface Lock. Ela fornece funcionalidades para o gerenciamento explícito de locks.
Principais métodos de ReentrantLock:
lock(): Captura um lock;unlock(): Libera o lock;tryLock(): Tenta capturar o lock e retorna true se a captura for bem-sucedida;tryLock(long timeout, TimeUnit unit): Tenta capturar o lock pelo tempo especificado;newCondition(): Cria uma condição para oLockatual.
Main.java
12345678910111213141516171819202122232425262728293031package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }
Como pode ser observado, no método increment() utilizamos o bloqueio com Lock. Quando uma thread entra no método, ela adquire o bloqueio em lock.lock(), executa o código e, em seguida, no bloco finally, liberamos o bloqueio em lock.unlock() para que outras threads possam acessar.
Liberamos o bloqueio no bloco finally por um motivo: este bloco é quase sempre executado, mesmo em casos de exceções, exceto quando o programa é finalizado.
Interface Condition
Só é possível criar um Condition vinculado a uma implementação específica de Lock. Por esse motivo, os métodos de Condition afetarão apenas o bloqueio daquela implementação específica de Lock.
Main.java
12private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();
Principais métodos de Condition:
await(): Aguarda um sinal de outra thread;signal(): Desbloqueia uma thread aguardando em uma condição;signalAll(): Desbloqueia todas as threads aguardando na condição.
Main.java
12345678910111213141516private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }
O método waitForCondition() bloqueia a thread até que a variável ready se torne true, o que indica que a condição foi atendida. Quando a condição é atendida, a thread continua em execução, exibindo a mensagem “Condition met!”
Quando o método await() é chamado, a thread é pausada e também libera o lock que capturou. Quando a thread acorda, ela deve capturar o lock novamente e só então começará a executar!
Exemplo de Código
Agora vamos analisar um exemplo de uso de ReentrantLock e Condition para gerenciar o registro em um evento:
Um trecho curto do vídeo
Bloqueio com ReentrantLock: O método register() adquire o bloqueio com lock.lock() para evitar que múltiplas threads executem o código ao mesmo tempo.
Condição com Condition: Se não houver espaços disponíveis, a thread chama spaceAvailable.await() para aguardar até que haja espaço disponível.
Liberação do bloqueio: Quando uma thread libera espaço usando o método cancel(), ela chama spaceAvailable.signalAll() para notificar todas as threads em espera.
Tratamento de exceções: O uso de blocos try-finally garante que o lock seja liberado mesmo que ocorra uma exceção.
O uso de Lock e Condition em Java permite um controle mais flexível sobre threads e sincronização do que o mecanismo tradicional de synchronized. Isso é especialmente útil em cenários complexos onde é necessário um controle mais preciso sobre threads e condições de espera.
Obrigado pelo seu feedback!
Pergunte à IA
Pergunte à IA
Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo
Awesome!
Completion rate improved to 3.33
Lock e Condition
Deslize para mostrar o menu
Em Java, a sincronização padrão é baseada na palavra-chave synchronized e em objetos monitor incorporados. No entanto, em alguns casos, o synchronized pode não ser suficiente, especialmente quando é necessária maior flexibilidade no gerenciamento de threads.
Descrição Geral
As interfaces Lock e Condition, introduzidas no pacote java.util.concurrent.locks, oferecem recursos avançados de gerenciamento de threads.
Nesta imagem, é possível observar que a primeira thread adquire o lock utilizando o método lock(), e nesse momento, outra thread não pode adquirir o mesmo lock. Assim que todo o código dentro do lock for executado, será chamado o método unlock() para liberar o lock. Somente após isso a segunda thread poderá adquirir o lock.
Diferença
A diferença entre essas duas interfaces é que as implementações de Lock são uma alternativa de alto nível ao bloco synchronized, e as implementações da interface Condition são uma alternativa aos métodos notify()/wait(). Ambas as interfaces fazem parte do pacote java.util.concurrent.locks.
Exemplos do Mundo Real
Imagine o gerenciamento de uma fila para inscrição em um evento. Para evitar excesso de inscrições e garantir a alocação adequada de assentos, é necessário utilizar mecanismos de bloqueio e condições para manter o fluxo de novas inscrições aguardando até que uma vaga esteja disponível.
Classe ReentrantLock
A classe ReentrantLock do pacote java.util.concurrent.locks é uma implementação da interface Lock. Ela fornece funcionalidades para o gerenciamento explícito de locks.
Principais métodos de ReentrantLock:
lock(): Captura um lock;unlock(): Libera o lock;tryLock(): Tenta capturar o lock e retorna true se a captura for bem-sucedida;tryLock(long timeout, TimeUnit unit): Tenta capturar o lock pelo tempo especificado;newCondition(): Cria uma condição para oLockatual.
Main.java
12345678910111213141516171819202122232425262728293031package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }
Como pode ser observado, no método increment() utilizamos o bloqueio com Lock. Quando uma thread entra no método, ela adquire o bloqueio em lock.lock(), executa o código e, em seguida, no bloco finally, liberamos o bloqueio em lock.unlock() para que outras threads possam acessar.
Liberamos o bloqueio no bloco finally por um motivo: este bloco é quase sempre executado, mesmo em casos de exceções, exceto quando o programa é finalizado.
Interface Condition
Só é possível criar um Condition vinculado a uma implementação específica de Lock. Por esse motivo, os métodos de Condition afetarão apenas o bloqueio daquela implementação específica de Lock.
Main.java
12private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();
Principais métodos de Condition:
await(): Aguarda um sinal de outra thread;signal(): Desbloqueia uma thread aguardando em uma condição;signalAll(): Desbloqueia todas as threads aguardando na condição.
Main.java
12345678910111213141516private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }
O método waitForCondition() bloqueia a thread até que a variável ready se torne true, o que indica que a condição foi atendida. Quando a condição é atendida, a thread continua em execução, exibindo a mensagem “Condition met!”
Quando o método await() é chamado, a thread é pausada e também libera o lock que capturou. Quando a thread acorda, ela deve capturar o lock novamente e só então começará a executar!
Exemplo de Código
Agora vamos analisar um exemplo de uso de ReentrantLock e Condition para gerenciar o registro em um evento:
Um trecho curto do vídeo
Bloqueio com ReentrantLock: O método register() adquire o bloqueio com lock.lock() para evitar que múltiplas threads executem o código ao mesmo tempo.
Condição com Condition: Se não houver espaços disponíveis, a thread chama spaceAvailable.await() para aguardar até que haja espaço disponível.
Liberação do bloqueio: Quando uma thread libera espaço usando o método cancel(), ela chama spaceAvailable.signalAll() para notificar todas as threads em espera.
Tratamento de exceções: O uso de blocos try-finally garante que o lock seja liberado mesmo que ocorra uma exceção.
O uso de Lock e Condition em Java permite um controle mais flexível sobre threads e sincronização do que o mecanismo tradicional de synchronized. Isso é especialmente útil em cenários complexos onde é necessário um controle mais preciso sobre threads e condições de espera.
Obrigado pelo seu feedback!