ReentrantLock

7 respostas
M

Olá a todos. Fiz uma pesquisa no fórum mas não achei nada que pudesse esclarecer minha dúvida.

Estou desenvolvendo uma aplicação que inicializa e executa algumas Threads, 5 por exemplo:

ExecutorService e = Executors.newFixedThreadPool(5);

for (int i = 0; i < 5; i++)
    e.execute(new RemoteTerminal());

Na execução dessas Threads, elas recebem uma referência para um objeto. Esse objeto
possui um método create.

MyTransaction transaction = Deck.getInstance().pickUpACard();
transaction.create(param1, param2);

Esse método create executa uma série de operações que eu preciso
que sejam feitas em modo exclusivo. Por isso coloquei o lock como vocês
podem ver abaixo:

public class NewOrderTransaction implements MyTransaction {

	private final Lock lock = new ReentrantLock();

	public void create(long param1, long param2) {
		lock.lock();
		try {
			//do op1
			//do op2
			System.out.println("teste");

		} finally {
			lock.unlock();
		}	
	}
}

Preparei para executar em modo debug e coloquei o breakpoint na linha do System.out.println(“teste”);

Quando começei a execução, foi possível ver na stack da VM (pelo Eclipse) que as 5 Threads estavam paradas na linha
do System.out.println(“teste”);

Ai surgiu minha dúvida. Isso deveria realmente acontecer? Já que essa linha está dentro de um lock, apenas uma Thread deveria executá-la
por vez. Certo?

Obrigado por enquanto

  • Murilo

7 Respostas

T
private final Lock lock = new ReentrantLock();

Por acaso você está confundindo “final” com “static”? É que pelo que estou vendo, para cada objeto "NewOrderTransaction " que você criar, um objeto “lock” será criado.

ViniGodoy

Aliás, para que você precisa do lock?

Existem objetos compartilhados? Se não existem, nem mesmo o lock é necessário.

M

@thingol: Obrigado pela resposta. Acho que é isso mesmo. Vou testar a noite e depois posto a resposta aqui.

@ViniGodoy: então, na verdade eu supostamente não deveria usar esse lock. Estou fazendo apenas como teste mesmo.
O código que coloquei é só um exemplo. Existem objetos compartilhados sim.

Pra ser mais preciso, meu problema começou com um controle de transação do Hibernate. A classe NewOrderTransaction na verdade é assim:

public class NewOrderTransaction implements MyTransaction {
	
	public void create(long param1, long param2)   {
		EntityManager em = null;
		Customer customer = null;		
		Warehouse warehouse = null;
		District district = null;
		Order order = null;
		EntityTransaction tx = null;
		
		try {
			em = JPAHelper.createEntityManager();			
			tx = em.getTransaction();
			tx.begin();
			
			warehouse = em.find(Warehouse.class, homeWarehouse);
			
			randomDistrict = ValuesGenerator.nextInt(1, 10);
			district = em.find(District.class, new DistrictPK(randomDistrict, warehouse.getId()));
			long nextOrderId = district.getNextOId();
			district.setNextOId(nextOrderId + 1);
			
			long customerId = ValuesGenerator.NURand(1023, 1, 3000);
			customer = em.find(Customer.class, new CustomerPK(customerId,
					district.getDistrictId(), homeWarehouse));
			
			order = OrderDAO.create(nextOrderId, customer);
			order.setCarrier(null);

			//Outras operações

			em.persist(order);
			tx.commit();
		} catch (Exception e) {
			if ( tx != null && tx.isActive() )
				tx.rollback();
		} finally {
			em.close();
		}
	}
}

O meu problema está na linha 20. Nessa linha a aplicação identifica qual deve ser o número dá próxima ordem de serviço para um determinado distrito. O distrito é escolhido aleatóriamente.

Considerando o cenário das 5 Threads, imagem que pelo menos duas delas receberam uma referência de NewOrderTransaction.

  1. As duas Threads inciam um bloco de transação
  2. Ambas recuperam um distrito. Vamos assumir que por coincidência, as duas Threads carregaram o mesmo distrito.
  3. Elas identificam o número da próxima ordem, que nesse caso será igual para as duas.
  4. As Threads criam os objetos Ordem
  5. Elas invocam o persist e depois fazem o commit da transação.

A primeira Thread a fazer isso executa sem problemas. A segunda Thread vai lançar uma DuplicateKeyException pq aquele número de ordem já existe na base.

Com base nisso ai vai minha pergunta:
a) Como faço pra controlar o nível de isolamento da transação no Hibernate num caso desses? Tem como fazer isso?

OBS: a PK do objeto Order é uam chave composta e por isso não posso usar SEQUENCE generator ou autoincrements da vida.

Obrigado

  • Murilo
M

thingol, você estava certo em relação ao lock.
Mudei para static e resolveu.

Agora se possível gostaria de uma ajuda no outro problema…

Obrigado

  • Murilo
M

Pra ficar mais claro, essa é a solução para o problema de isolamento de transações do Hibernate. A solução para o Lock já tinha sido colocada numa resposta anterior. Desculpem pela confusão…

Bom, vamos lá. Não sei se é a melhor ou mais correta, mas no meu caso funcionou.
A classe NewOrderTransaction ficou assim:

public class NewOrderTransaction implements TpccUfprTransaction {
	
	public void create(long param1, long param2) {
		EntityManager em = JPAHelper.createEntityManager();

		try {
			em.getTransaction().begin();
			
			warehouse = em.find(Warehouse.class, param1);
			
		                district = em.find(District.class, new DistrictPK(1, warehouse.getId()));
			em.lock(district, LockModeType.WRITE);
			em.refresh(district);
			long nextOrderId = district.getNextOId();
			district.setNextOId(nextOrderId + 1);
			
			em.getTransaction().commit();

Considerando que 3 Threads (t1, t2 e t3) estão em execução e todas executarão esse pedaço de código, ocorre o seguinte:

  • t1, t2 e t3 recuperam o mesmo distrito
  • Ao tentar fazer o lock da entidade, apenas uma vai conseguir (digamos q seja t1). As outras threads (t2 e t3) que não conseguiram travar o objeto, serão colocadas numa fila de Threads bloqueadas.
  • t1 faz refresh do objeto que neste caso nao tem efeito nenhum
  • t1 atualiza um campo do distrito em questão
  • Quando do commit, a alteração é gravada na base e uma das Threads que estava bloqueada, ganha controle sobre o objeto
  • O comando em.refresh() atualiza a instância q t2 tem, refletindo assim as alterações feitas por t1
  • t2 atualiza novamente o atributo
  • Faz commit e assim segue o fluxo…

Testei com 5 Threads executando essa mesma parte e funcionou.

Por favor, corrijam-me se eu estiver errado.
Sugestões serão muito bem aceitas também.

  • Murilo
T

Que esquisito, usar lock com bancos de dados dessa maneira. Quem deveria tratar o lock não é o banco de dados?

M

Concordo com você thingol, mas foi a única maneira que encontrei para resolver essa situação…
Talvez se ao invés do em.find() fosse feito um SELECT … FOR UPDATE o BD saberia que tal linha deveria ser travada. Mas nem todos os BDs aceitam esse comando…

Criado 26 de maio de 2008
Ultima resposta 27 de mai. de 2008
Respostas 7
Participantes 3