Bem-vindo ao tutorial de exemplo de Java Lock. Normalmente, ao trabalhar em um ambiente de múltiplas threads, usamos synchronized para segurança de thread.
Java Lock
Na maioria das vezes, a palavra-chave synchronized é a escolha ideal, mas possui algumas limitações que levaram à inclusão da API Lock no pacote de Concorrência do Java. A API de Concorrência do Java 1.5 apresentou o pacote
java.util.concurrent.locks
com a interface Lock
e algumas classes de implementação para aprimorar o mecanismo de bloqueio de objetos. Algumas interfaces e classes importantes na API de Lock do Java são:
-
Lock: Esta é a interface base para a API Lock. Ela fornece todas as funcionalidades da palavra-chave synchronized, com maneiras adicionais de criar diferentes Condições para bloqueio, fornecendo tempo limite para a thread esperar pelo bloqueio. Alguns dos métodos importantes são lock() para adquirir o bloqueio, unlock() para liberar o bloqueio, tryLock() para esperar pelo bloqueio por um determinado período de tempo, newCondition() para criar a Condição etc.
-
Condição: Objetos de Condição são similares ao modelo de Espera-Notificação de Objeto com recurso adicional de criar diferentes conjuntos de espera. Um objeto de Condição é sempre criado por um objeto de Bloqueio. Alguns dos métodos importantes são await() que é similar ao wait() e signal(), signalAll() que é similar aos métodos notify() e notifyAll().
-
Bloqueio de Leitura-Escrita: Contém um par de bloqueios associados, um para operações somente leitura e outro para escrita. O bloqueio de leitura pode ser mantido simultaneamente por múltiplas threads leitoras desde que não haja threads escritoras. O bloqueio de escrita é exclusivo.
-
ReentrantLock: Esta é a classe de implementação mais amplamente utilizada da interface Lock. Esta classe implementa a interface Lock de forma semelhante à palavra-chave synchronized. Além da implementação da interface Lock, ReentrantLock contém alguns métodos utilitários para obter a thread que possui a trava, threads esperando para adquirir a trava etc. Os blocos synchronized são reentrantes por natureza, ou seja, se uma thread possui a trava no objeto monitor e se outro bloco synchronized requer a trava no mesmo objeto monitor, então a thread pode entrar nesse bloco de código. Acredito que este seja o motivo pelo qual o nome da classe é ReentrantLock. Vamos entender esse recurso com um exemplo simples.
public class Test{ public synchronized foo(){ //faça algo bar(); } public synchronized bar(){ //faça algo mais } }
Se uma thread entra em foo(), ela possui a trava no objeto Test, então quando tenta executar o método bar(), a thread é permitida a executar o método bar(), pois já possui a trava no objeto Test, ou seja, o mesmo que synchronized(this).
Exemplo de Lock em Java – ReentrantLock em Java
Agora vamos ver um exemplo simples onde substituiremos a palavra-chave synchronized pela API de Lock do Java. Digamos que temos uma classe Resource com algumas operações onde queremos que seja thread-safe e alguns métodos onde a segurança da thread não é necessária.
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//faça alguma operação, leitura de BD, escrita etc
}
public void doLogging(){
//registro, não precisa de segurança de thread
}
}
Agora, vamos supor que temos uma classe Runnable onde usaremos os métodos de Recurso.
package com.journaldev.threads.lock;
public class SynchronizedLockExample implements Runnable{
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
Observe que estou usando um bloco sincronizado para adquirir o bloqueio no objeto de Recurso. Poderíamos ter criado um objeto fictício na classe e o usado para fins de bloqueio. Agora, vejamos como podemos usar a API de Lock do Java e reescrever o programa acima sem usar a palavra-chave synchronized. Usaremos ReentrantLock no Java.
package com.journaldev.threads.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//liberar o bloqueio
lock.unlock();
}
resource.doLogging();
}
}
Como você pode ver, estou usando o método tryLock() para garantir que minha thread espere apenas por um tempo definido e, se não conseguir o bloqueio no objeto, está apenas registrando e saindo. Outro ponto importante a ser observado é o uso do bloco try-finally para garantir que o bloqueio seja liberado mesmo se a chamada do método doSomething() lançar alguma exceção.
Java Lock vs synchronized
Com base nos detalhes e no programa acima, podemos facilmente concluir as seguintes diferenças entre o Lock do Java e a sincronização.
- A API de Lock do Java fornece mais visibilidade e opções para bloqueio, ao contrário da sincronização, onde uma thread pode acabar esperando indefinidamente pelo bloqueio, podemos usar tryLock() para garantir que a thread espere apenas por um tempo específico.
- O código de sincronização é muito mais limpo e fácil de manter, enquanto com Lock somos obrigados a ter um bloco try-finally para garantir que o Lock seja liberado mesmo se alguma exceção for lançada entre as chamadas dos métodos lock() e unlock().
- bloqueios ou métodos de sincronização podem abranger apenas um método, enquanto podemos adquirir o bloqueio em um método e liberá-lo em outro método com a API de Lock.
- A palavra-chave synchronized não fornece equidade, enquanto podemos definir a equidade como verdadeira ao criar um objeto ReentrantLock, para que a thread que espera mais tempo obtenha o bloqueio primeiro.
- Podemos criar diferentes condições para o Lock e threads diferentes podem aguardar() por diferentes condições.
Isso é tudo para o exemplo de Lock em Java, ReentrantLock em Java e uma análise comparativa com a palavra-chave synchronized.
Source:
https://www.digitalocean.com/community/tutorials/java-lock-example-reentrantlock