Exemplo de Bloqueio Java – ReentrantLock

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:

  1. 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.

  2. 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().

  3. 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.

  4. 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.

  1. 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.
  2. 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().
  3. 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.
  4. 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.
  5. 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