Bem-vindo ao tutorial de exemplo de Java Lock. Normalmente, ao trabalhar com ambiente multi-threaded, usamos synchronized para segurança de threads.
Java Lock
Na maioria das vezes, a palavra-chave synchronized é a escolha, mas ela 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 veio com o pacote
java.util.concurrent.locks
com a interface Lock
e algumas classes de implementação para melhorar o mecanismo de bloqueio de objetos. Algumas interfaces e classes importantes na API Lock do Java são:
-
Lock: Esta é a interface base para a API Lock. Ela fornece todas as funcionalidades da palavra-chave synchronized com formas 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 aguardar o bloqueio por um certo período de tempo, newCondition() para criar a Condição etc.
-
Condição: Objetos de Condição são semelhantes ao modelo wait-notify de Objeto com recurso adicional para criar diferentes conjuntos de espera. Um objeto de Condição é sempre criado pelo objeto de Bloqueio. Alguns dos métodos importantes são await() que é semelhante ao wait() e signal(), signalAll() que é semelhante aos métodos notify() e notifyAll().
-
Bloqueio de Leitura/Escrita: Ele contém um par de bloqueios associados, um para operações apenas de leitura e outro para escrita. O bloqueio de leitura pode ser mantido simultaneamente por várias 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 o thread que está segurando o bloqueio, threads esperando para adquirir o bloqueio, etc. Blocos synchronized são reentrantes por natureza, ou seja, se um thread possui o bloqueio no objeto monitor e se outro bloco synchronized requer ter o bloqueio no mesmo objeto monitor, então o thread pode entrar nesse bloco de código. Acredito que esta seja a razão para o nome da classe ser ReentrantLock. Vamos entender esse recurso com um exemplo simples.
public class Test{ public synchronized foo(){ //fazer algo bar(); } public synchronized bar(){ //fazer mais algo } }
Se um thread entra em foo(), ele tem o bloqueio no objeto Test, então quando tenta executar o método bar(), o thread tem permissão para executar o método bar() já que está segurando o bloqueio no objeto Test, ou seja, o mesmo que synchronized(this).
Exemplo de Bloqueio em Java – ReentrantLock em Java
Agora vamos ver um exemplo simples onde substituiremos a palavra-chave synchronized pela API de Bloqueio em Java. Digamos que temos uma classe Resource com algumas operações onde queremos que seja seguro para threads e alguns métodos onde a segurança de threads não é necessária.
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//faça alguma operação, leitura de banco de dados, escrita etc
}
public void doLogging(){
//logging, não precisa de segurança para threads
}
}
Agora, vamos supor que temos uma classe Runnable onde usaremos métodos de Recursos.
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 Recurso. Poderíamos ter criado um objeto fictício na classe e o usado para fins de bloqueio. Agora, vamos ver como podemos usar a API de Lock do Java e reescrever o programa acima sem usar a palavra-chave synchronized. Vamos usar 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 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, ele apenas faz log e sai. 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 Java Lock 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 o 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 cobrir apenas um método, enquanto podemos adquirir o bloqueio em um método e liberá-lo em outro método com a API de Bloqueio.
- A palavra-chave synchronized não fornece justiça, enquanto podemos definir a justiça como verdadeira ao criar um objeto ReentrantLock, para que a thread de espera mais longa obtenha o bloqueio primeiro.
- Podemos criar condições diferentes para Bloqueio e diferentes threads podem aguardar() por diferentes condições.
Isso é tudo para o exemplo de Bloqueio 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