Erro "java.lang.IllegalMonitorStateException" [RESOLVIDO]

Estou tendo problemas onde uma Thread está lançando IllegalMonitorStateException, mas não consigo determinar qual é a causa.

Esta é a classe:

package com.ats.ecf.cliente.service.impl;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.ats.ecf.cliente.service.ActiveSincronizacaoECFService;
import com.ats.ecf.cliente.service.SincronizacaoECFService;
import com.ats.framework.exception.ConfigurationException;

public class ActiveSincronizacaoECFServiceImpl implements
		ActiveSincronizacaoECFService {
	private static final Logger logger = Logger.getLogger(ActiveSincronizacaoECFServiceImpl.class);
	
	private Map<String, SincronizacaoECFService> tarefas;
	
	private final Set<String> agendados = new HashSet<String>();
	private final Thread thread = new Thread("Sincronização de dados de ECF") {
		@Override
		public void run() {
			while (this.isAlive()) {
				executaSincronizacao();
			}
		}		
	};
	
	public void inicializa() {
		thread.start();
	}

	@Override
	public void agenda(String nomeTarefa) {
		synchronized (this.agendados) {
			this.agendados.add(nomeTarefa);
			this.notify(); // Avisa que houve um agendamento.
		}
	}

	@Override
	public void sincroniza() {
		for (String nomeTarefa : this.getTarefas().keySet()) {
			this.agenda(nomeTarefa);
		}
	}
	
	private boolean checkAgenda(String nomeTarefa) {
		synchronized (this.agendados) {
			boolean marcado = this.agendados.contains(nomeTarefa);
			this.agendados.remove(nomeTarefa);
			return marcado;
		}		
	}
	
	private boolean existeAgendamento() {
		synchronized (this.agendados) {
			return !this.agendados.isEmpty();
		}
	}
	
	private void aguardaAgendamento() {
		logger.debug("Aguardando agendamento.");
		while (!this.existeAgendamento()) {
			try {
				this.wait(); // Aguarda até que algum agendamento ocorra.
			} catch (InterruptedException e) {
				logger.debug("Pausa para agendamento interrompida.", e);
			}
		}
	}
	
	private void executaSincronizacao() {
		this.aguardaAgendamento();
		
		logger.debug("Executando tarefas de sincronização agendadas.");
		for (Map.Entry<String, SincronizacaoECFService> entry : this.getTarefas().entrySet()) {
			if (this.checkAgenda(entry.getKey())) {
				if (logger.isDebugEnabled()) {
					logger.debug("Executando " + entry.getKey());
				}
				entry.getValue().sincroniza();
			}
		}
	}

	public Map<String, SincronizacaoECFService> getTarefas() {
		if (this.tarefas == null) {
			throw new ConfigurationException("Tarefas não informadas.");
		}
		return this.tarefas;
	}
	public void setTarefas(Map<String, SincronizacaoECFService> tarefas) {
		this.tarefas = tarefas;
	}

}

Este é o teste unitário:

package com.ats.ecf.cliente.service.test;

import com.ats.ecf.cliente.service.SincronizacaoECFService;
import com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl;
import com.ats.framework.util.map.MapBuilder;

import junit.framework.TestCase;

public class ActiveSincronizacaoECFServiceTest extends TestCase {

	public void testSincronizacao() throws InterruptedException {
		ActiveSincronizacaoECFServiceImpl service = new ActiveSincronizacaoECFServiceImpl();
		SincronizacaoTestClass sinc1 = new SincronizacaoTestClass();
		SincronizacaoTestClass sinc2 = new SincronizacaoTestClass();
		service.setTarefas(new MapBuilder<String, SincronizacaoECFService>()
				.put("a", sinc1).put("b", sinc2).getMap());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.inicializa();
		Thread.sleep(500);
		assertFalse(sinc1.isInvocado());
		assertFalse(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.agenda("a");
		Thread.sleep(500);
		assertTrue(sinc1.isInvocado());
		assertFalse(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.agenda("b");
		Thread.sleep(500);
		assertFalse(sinc1.isInvocado());
		assertTrue(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.agenda("a");
		service.agenda("b");
		Thread.sleep(500);
		assertTrue(sinc1.isInvocado());
		assertTrue(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.sincroniza();
		Thread.sleep(500);
		assertTrue(sinc1.isInvocado());
		assertTrue(sinc2.isInvocado());
	}
	
	private static class SincronizacaoTestClass implements SincronizacaoECFService {
		
		private boolean invocado;

		@Override
		public void sincroniza() {
			this.setInvocado(true);
		}

		public boolean isInvocado() {
			return invocado;
		}
		public void setInvocado(boolean invocado) {
			this.invocado = invocado;
		}
		
	}

}

Esta é a exceção:

Exception in thread "Sincronização de dados de ECF" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:485)
	at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl.aguardaAgendamento(ActiveSincronizacaoECFServiceImpl.java:66)
	at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl.executaSincronizacao(ActiveSincronizacaoECFServiceImpl.java:74)
	at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl.access$0(ActiveSincronizacaoECFServiceImpl.java:73)
	at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl$1.run(ActiveSincronizacaoECFServiceImpl.java:24)

[quote=haroldo-ok-ats]Estou tendo problemas onde uma Thread está lançando IllegalMonitorStateException, mas não consigo determinar qual é a causa.

Esta é a classe:

package com.ats.ecf.cliente.service.impl;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.ats.ecf.cliente.service.ActiveSincronizacaoECFService;
import com.ats.ecf.cliente.service.SincronizacaoECFService;
import com.ats.framework.exception.ConfigurationException;

public class ActiveSincronizacaoECFServiceImpl implements
		ActiveSincronizacaoECFService {
	private static final Logger logger = Logger.getLogger(ActiveSincronizacaoECFServiceImpl.class);
	
	private Map<String, SincronizacaoECFService> tarefas;
	
	private final Set<String> agendados = new HashSet<String>();
	private final Thread thread = new Thread("Sincronização de dados de ECF") {
		@Override
		public void run() {
			while (this.isAlive()) {
				executaSincronizacao();
			}
		}		
	};
	
	public void inicializa() {
		thread.start();
	}

	@Override
	public void agenda(String nomeTarefa) {
		synchronized (this.agendados) {
			this.agendados.add(nomeTarefa);
			this.notify(); // Avisa que houve um agendamento.
		}
	}

	@Override
	public void sincroniza() {
		for (String nomeTarefa : this.getTarefas().keySet()) {
			this.agenda(nomeTarefa);
		}
	}
	
	private boolean checkAgenda(String nomeTarefa) {
		synchronized (this.agendados) {
			boolean marcado = this.agendados.contains(nomeTarefa);
			this.agendados.remove(nomeTarefa);
			return marcado;
		}		
	}
	
	private boolean existeAgendamento() {
		synchronized (this.agendados) {
			return !this.agendados.isEmpty();
		}
	}
	
	private void aguardaAgendamento() {
		logger.debug("Aguardando agendamento.");
		while (!this.existeAgendamento()) {
			try {
				this.wait(); // Aguarda até que algum agendamento ocorra.
			} catch (InterruptedException e) {
				logger.debug("Pausa para agendamento interrompida.", e);
			}
		}
	}
	
	private void executaSincronizacao() {
		this.aguardaAgendamento();
		
		logger.debug("Executando tarefas de sincronização agendadas.");
		for (Map.Entry<String, SincronizacaoECFService> entry : this.getTarefas().entrySet()) {
			if (this.checkAgenda(entry.getKey())) {
				if (logger.isDebugEnabled()) {
					logger.debug("Executando " + entry.getKey());
				}
				entry.getValue().sincroniza();
			}
		}
	}

	public Map<String, SincronizacaoECFService> getTarefas() {
		if (this.tarefas == null) {
			throw new ConfigurationException("Tarefas não informadas.");
		}
		return this.tarefas;
	}
	public void setTarefas(Map<String, SincronizacaoECFService> tarefas) {
		this.tarefas = tarefas;
	}

}

Este é o teste unitário:

package com.ats.ecf.cliente.service.test;

import com.ats.ecf.cliente.service.SincronizacaoECFService;
import com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl;
import com.ats.framework.util.map.MapBuilder;

import junit.framework.TestCase;

public class ActiveSincronizacaoECFServiceTest extends TestCase {

	public void testSincronizacao() throws InterruptedException {
		ActiveSincronizacaoECFServiceImpl service = new ActiveSincronizacaoECFServiceImpl();
		SincronizacaoTestClass sinc1 = new SincronizacaoTestClass();
		SincronizacaoTestClass sinc2 = new SincronizacaoTestClass();
		service.setTarefas(new MapBuilder<String, SincronizacaoECFService>()
				.put("a", sinc1).put("b", sinc2).getMap());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.inicializa();
		Thread.sleep(500);
		assertFalse(sinc1.isInvocado());
		assertFalse(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.agenda("a");
		Thread.sleep(500);
		assertTrue(sinc1.isInvocado());
		assertFalse(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.agenda("b");
		Thread.sleep(500);
		assertFalse(sinc1.isInvocado());
		assertTrue(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.agenda("a");
		service.agenda("b");
		Thread.sleep(500);
		assertTrue(sinc1.isInvocado());
		assertTrue(sinc2.isInvocado());

		sinc1.setInvocado(false);
		sinc2.setInvocado(false);
		service.sincroniza();
		Thread.sleep(500);
		assertTrue(sinc1.isInvocado());
		assertTrue(sinc2.isInvocado());
	}
	
	private static class SincronizacaoTestClass implements SincronizacaoECFService {
		
		private boolean invocado;

		@Override
		public void sincroniza() {
			this.setInvocado(true);
		}

		public boolean isInvocado() {
			return invocado;
		}
		public void setInvocado(boolean invocado) {
			this.invocado = invocado;
		}
		
	}

}

Esta é a exceção:

Exception in thread "Sincronização de dados de ECF" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:485) at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl.aguardaAgendamento(ActiveSincronizacaoECFServiceImpl.java:66) at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl.executaSincronizacao(ActiveSincronizacaoECFServiceImpl.java:74) at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl.access$0(ActiveSincronizacaoECFServiceImpl.java:73) at com.ats.ecf.cliente.service.impl.ActiveSincronizacaoECFServiceImpl$1.run(ActiveSincronizacaoECFServiceImpl.java:24) [/quote]

java.lang.IllegalMonitorStateException

Essa exception é pq em algum lugar do seu codigo vc esta usando “wait ou notfy” de algum objeto não sincronizado. Voce so pode usar esses métodos em um contexto syncrhonized.

E por um acaso é aqui que esta gerando á exceção:

private void aguardaAgendamento() { logger.debug("Aguardando agendamento."); while (!this.existeAgendamento()) { try { this.wait(); // ==> AQUI <== } catch (InterruptedException e) { logger.debug("Pausa para agendamento interrompida.", e); } } }

[quote=evertonsilvagomesjava]E por um acaso é aqui que esta gerando á exceção:

private void aguardaAgendamento() { logger.debug("Aguardando agendamento."); while (!this.existeAgendamento()) { try { this.wait(); // ==> AQUI <== } catch (InterruptedException e) { logger.debug("Pausa para agendamento interrompida.", e); } } } [/quote]

Hã… sim, eu notei isso. :stuck_out_tongue:
O wait() só é chamado em um único lugar, e apenas por uma única Thread, o notify() só é chamado em um único lugar.
A questão é: porquê a exceção está sendo gerada, e como corrigí-la.

[quote=haroldo-ok-ats][quote=evertonsilvagomesjava]E por um acaso é aqui que esta gerando á exceção:

private void aguardaAgendamento() { logger.debug("Aguardando agendamento."); while (!this.existeAgendamento()) { try { this.wait(); // ==> AQUI <== } catch (InterruptedException e) { logger.debug("Pausa para agendamento interrompida.", e); } } } [/quote]

Hã… sim, eu notei isso. :stuck_out_tongue:
O wait() só é chamado em um único lugar, e apenas por uma única Thread, o notify() só é chamado em um único lugar.
A questão é: porquê a exceção está sendo gerada, e como corrigí-la.[/quote]

Pra corrigir vc tem que sincronizar “this”.

Ah, OK, fazendo as alterações, abaixo, a classe executou exatamente da forma esperada:

	@Override
	public void agenda(String nomeTarefa) {
		synchronized (this.agendados) {
			this.agendados.add(nomeTarefa);
			synchronized (this) {
				this.notify(); // Avisa que houve um agendamento.
			}
		}
	}
	private void aguardaAgendamento() {
		logger.debug("Aguardando agendamento.");
		while (!this.existeAgendamento()) {
			try {
				synchronized (this) {
					this.wait(); // Aguarda até que algum agendamento ocorra.										
				}
			} catch (InterruptedException e) {
				logger.debug("Pausa para agendamento interrompida.", e);
			}
		}
	}

Valeu pela ajuda! :slight_smile:

[quote=haroldo-ok-ats]Ah, OK, fazendo as alterações, abaixo, a classe executou exatamente da forma esperada:

	@Override
	public void agenda(String nomeTarefa) {
		synchronized (this.agendados) {
			this.agendados.add(nomeTarefa);
			synchronized (this) {
				this.notify(); // Avisa que houve um agendamento.
			}
		}
	}
	private void aguardaAgendamento() {
		logger.debug("Aguardando agendamento.");
		while (!this.existeAgendamento()) {
			try {
				synchronized (this) {
					this.wait(); // Aguarda até que algum agendamento ocorra.										
				}
			} catch (InterruptedException e) {
				logger.debug("Pausa para agendamento interrompida.", e);
			}
		}
	}

Valeu pela ajuda! :slight_smile: [/quote]

Isso ai, tinha colocado a resposta dentro do “quote” kk.