Threads - Exercício de Faculdade

Rogério,
Fiz um teste com esta sua ultima versão, colocando 5.0 no saldo inicial e no loop, novamente nao funcionou, como eu disse antes, para ser considerado ok, precisa funcionar com qualquer saldo inicial, veja a saida do meu testes:

Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
C1: 5.0 C2: 11.0 C3: 13.0 C4: 9.0
Tempo: 1

[quote=GilsonNunes]tente assim:
fiz aki mts testes e tds ok.
[/quote]

Gilson, fiz vários testes e no inicio realmente parece ok, mas se vc insisitir vai se deparar com inconsistencias, coloquei 5 no loop e no saldo inicial, veja a saida do meu ultimo teste:

Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
C1: 5.0 C2: 13.0 C3: 12.0 C4: 7.0
Tempo: 1

Como o Gilson falou antes, pra funcionar sempre com qualquer saldo tem que tirar a condição de verificação para valores negativos. Só porque com 5 no saldo o resultado final não foi 5 no final não significa que está errado. Executar várias vezes com o mesmo saldo e obter dados diferentes também não quer dizer que está errado porque com threads você não tem controle sobre a ordem das operações. Dependendo da orgem de execução das operações das threads em alguns momentos o saldo não vai ser suficiente mesmo. Você até poderia fazer a thread esperar no método debitar até que o valor fosse suficiente para atender o débito, mas como você não tem controle sobre a ordem das operações quando utiliza threads pode ser que o valor nunca seja suficiente para atender aquela solicitação e a thread fique “esperando para sempre”.

Bom dia galera.

Primeiro deixe-me dizer que não sou expert em JAVA, e muito menos gosto de JAVA.
Meu negócio é C/C++

Mas conceitos são conceitos. Então… Vamos lá

Leia direito a parte grifada do enunciado do exercício. Você notará que não condiz com o código do método transferir:

public void transferir( AgenteConta c, double quantia ) { c.saldo = c.saldo - quantia; this.saldo += quantia; }

Você deve tirar ‘quantia’ da conta atual e somar essa ‘quantia’ para conta ‘c’, como abaixo:

public void transferir( AgenteConta c, double quantia ) { c.saldo = c.saldo + quantia; this.saldo -= quantia; }

Logo, equivale a:

public void transferir( AgenteConta c, double quantia ) { c.debitar(quantia); this.creditar(quantia); }

Observe que fazendo isso e não colocando trava alguma(synchronized,lock e etc), a probabilidade da mensagem "Saldo Insuficiente" aparecer é muitissimo pequena.
Execute, teste e vc verá.

Agora existem varias maneiras de se resolver:

Vamos explorar os monitores globais, encontrei duas formas:

vc pode colocar um monitor global estático(da classe) no método ‘run’, veja abaixo:

[code]public void run() {
for (long i = 0; i < 1000000; i++) {
synchronized (AgenteConta.class) {

			contas[0].debitar(1);
			contas[1].debitar(1);
			contas[2].debitar(1);

			this.creditar(3);
			this.transferir(contas[0], 1);
			this.transferir(contas[1], 1);
			this.transferir(contas[2], 1);
		}
	}

}[/code]

OU, usando um monitor global nos métodos ‘debitar’ e ‘creditar’ com uma instância de ‘AgenteConta’, o único detalhe é que vc precisa analisar qual instância as Threads têm em comum, como abaixo:

[code]public void creditar(double valor) {
synchronized (contas[1].contas[2]) {
saldo+=valor;
}
}

public void debitar(double valor) {
synchronized(contas[1].contas[2]){
if(valor>saldo){
System.out.println("Saldo insuficiente");
}
else{
saldo-=valor;
}
}
}[/code]

Note que fazendo isso o tempo de execução aumenta conforme aumenta-se o valor do saldo.(Obs: teste feito com 1000000,1000,50 e 5)

Ou, Usando a suposta “trava explicita”, sem monitor global, alterando os métodos ‘debitar’ e ‘creditar’:

[code]public synchronized void debitar(double valor) {
while(saldo<=0){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(valor>saldo){
System.out.println("Saldo insuficiente");
}
else{
saldo-=valor;
}
notifyAll();
}

public synchronized void creditar(double valor) {
saldo+=valor;
notifyAll();
}[/code]

Muito bem, a listagem completa pro seu problema obedecendo as restrições impostas, fica assim:

[code]public class AgenteConta implements Runnable {
private double saldo = 1000000;
private AgenteConta[] contas = new AgenteConta[3];

public AgenteConta(AgenteConta c1, AgenteConta c2, AgenteConta c3) {
	contas[0] = c1;
	contas[1] = c2;
	contas[2] = c3;

}

public AgenteConta() {

}

public synchronized void debitar(double valor) {
	while (saldo &lt;= 0) {
		try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	if (valor &gt; saldo) {
		System.out.println(&quot;Saldo insuficiente&quot;);
	} else {
		saldo -= valor;
	}
	notifyAll();
}

public synchronized void creditar(double valor) {
	saldo += valor;
	notifyAll();
}

public void transferir(AgenteConta c, double quantia) {
	c.creditar(quantia);
	this.debitar(quantia);
}

public void run() {
	for (long i = 0; i &lt; 1000000; i++) {

		contas[0].debitar(1);
		contas[1].debitar(1);
		contas[2].debitar(1);

		this.creditar(3);
		this.transferir(contas[0], 1);
		this.transferir(contas[1], 1);
		this.transferir(contas[2], 1);
	}

}

/**
 * @param args
 * @throws InterruptedException
 */
public static void main(String[] args) throws InterruptedException {
	long t = System.currentTimeMillis();
	AgenteConta c1 = new AgenteConta();
	AgenteConta c2 = new AgenteConta();
	AgenteConta c3 = new AgenteConta();
	AgenteConta c4 = new AgenteConta(c1, c2, c3);
	c1.contas = new AgenteConta[] { c2, c3, c4 };
	c2.contas = new AgenteConta[] { c1, c3, c4 };
	c3.contas = new AgenteConta[] { c1, c2, c4 };
	Thread t1 = new Thread(c1);

	Thread t2 = new Thread(c2);

	Thread t3 = new Thread(c3);

	Thread t4 = new Thread(c4);

	t1.start();
	t2.start();
	t3.start();
	t4.start();

	try {
		t1.join();
		t2.join();
		t3.join();
		t4.join();
	} catch (InterruptedException e) {
		System.out.println(&quot;Erro&quot; + e.getMessage());
	}
	System.out.println(&quot;C1: &quot; + c1.saldo + &quot; C2: &quot; + c2.saldo + &quot; C3: &quot;
			+ c3.saldo + &quot; C4: &quot; + c4.saldo);
	System.out.println(&quot; Tempo: &quot; + (System.currentTimeMillis() - t));
}

}
[/code]

Valeu Galera é isso aí. Qualquer coisa que eu tiver dito errado, podem ficar a vontade para postar uma correção.

Pelo que vi no enunciado parece que precisa mudar o sinal das operações no método de transferência, ele está fazendo o contrário do que está escrito na questão.
Bom, então na minha implementação é só mudar a ordem no método transferir. E eu mudei também o método run pra fazer vários testes em sequência.

package teste;


public class AgenteContaFinal implements Runnable
{
	private double saldo = 5;
	private long qtde;
	
	private AgenteContaFinal[] contas = new AgenteContaFinal[ 3 ];
	
	public AgenteContaFinal( )
	{
	}

	public AgenteContaFinal( AgenteContaFinal c1, AgenteContaFinal c2, AgenteContaFinal c3 )
	{
		contas[ 0 ] = c1;
		contas[ 1 ] = c2;
		contas[ 2 ] = c3;
	}

	public synchronized void debitar( double valor )
	{
		if( valor <= saldo ) {
			saldo -= valor;
		} else {
			System.out.println("Insuficiente");
		}
	}

	public synchronized void creditar( double valor )
	{
		saldo += valor;
	}

	public void transferir( AgenteContaFinal c, double quantia )
	{
		int hash = System.identityHashCode(this);
		int hashOutroObjeto = System.identityHashCode(c);
		Object lockA = null;
		Object lockB = null;
		if(hash < hashOutroObjeto) {
			lockA = this;
			lockB = c;
		} else {
			lockA = c;
			lockB = this;
		}
		
		synchronized(lockA) {
			synchronized (lockB) {
				if ( quantia <= saldo ) {
					c.saldo = c.saldo + quantia;
					this.saldo -= quantia;
				} else {
					System.out.println("Saldo insuficiente");
				}
			}
		}
		
	}

	public void run( )
	{
		for ( long i = 0; i < qtde; i++ )
		{
			contas[ 0 ].debitar( 1 );
			contas[ 1 ].debitar( 1 );
			contas[ 2 ].debitar( 1 );
			this.creditar( 3 );
			this.transferir( contas[ 0 ], 1 );
			this.transferir( contas[ 1 ], 1 );
			this.transferir( contas[ 2 ], 1 );
		}
	}

	public static void main( String[] args )
	{
		
    	for(long q = 5; q < 100; q++) {
	        long t = System.currentTimeMillis( );  
	        AgenteContaFinal c1 = new AgenteContaFinal();  
	        AgenteContaFinal c2 = new AgenteContaFinal();  
	        AgenteContaFinal c3 = new AgenteContaFinal();  
	        AgenteContaFinal c4 = new AgenteContaFinal( c1, c2, c3);  
	        c1.qtde=q;
	        c2.qtde=q;
	        c3.qtde=q;
	        c4.qtde=q;
	        c1.saldo=q;
	        c2.saldo=q;
	        c3.saldo=q;
	        c4.saldo=q;
	        
	        c1.contas = new AgenteContaFinal[] { c2, c3, c4 };  
	        c2.contas = new AgenteContaFinal[] { c1, c3, c4 };  
	        c3.contas = new AgenteContaFinal[] { c1, c2, c4 };  
	        Thread t1 = new Thread( c1 );  
	        Thread t2 = new Thread( c2 );  
	        Thread t3 = new Thread( c3 );  
	        Thread t4 = new Thread( c4 );  
	
	        t1.start( );
	        t2.start( );  
	        t3.start( );  
	        t4.start( );  
	
	        try  
	        {  
	            t1.join( );  
	            t2.join( );  
	            t3.join( );  
	            t4.join( );  
	        }  
	        catch ( InterruptedException e )  
	        {  
	            System.out.println( "Erro" + e.getMessage( ) );  
	        }  
	        System.out.println( " C1: " + c1.saldo + " C2: " + c2.saldo + " C3: "  
	                + c3.saldo + " C4: " + c4.saldo );  
	        
	        Double saldoS1 = c1.saldo;
	        Double saldoS2 = c2.saldo;
	        Double saldoS3 = c3.saldo;
	        Double saldoS4 = c4.saldo;
	        if( saldoS1.compareTo(saldoS2) != 0 ||
	            saldoS1.compareTo(saldoS3) != 0 ||
	            saldoS1.compareTo(saldoS4) != 0 ||	        		
	            
		        saldoS2.compareTo(saldoS1) != 0 ||
			    saldoS2.compareTo(saldoS3) != 0 ||
			    saldoS2.compareTo(saldoS4) != 0 ||	        		

		        saldoS3.compareTo(saldoS1) != 0 ||
			    saldoS3.compareTo(saldoS2) != 0 ||
			    saldoS3.compareTo(saldoS4) != 0 ||	        		

		        saldoS4.compareTo(saldoS1) != 0 ||
			    saldoS4.compareTo(saldoS2) != 0 ||
			    saldoS4.compareTo(saldoS3) != 0
	        ) {
	        	throw new RuntimeException("Ocorreu algum problema na ordem de execução das operações!");
	        }
	        	
	        System.out.println( " Tempo: " + (System.currentTimeMillis( ) - t) );  
    	}
		
		
	}
}

Agora que estou lendo as outras mensagens vi que já tinha percebido o que eu falei :slight_smile:
Sacanagem desse professor, kkk

Eu fiquei com uma dúvida. No método debitar você espera o saldo ficar maior que zero. Neste exemplo específico não tem problema porque da forma que está o looping sempre alguma thread vai creditar em algum momento o valor necessário para o débito. Mas num código para qualquer quantia de créditos/débitos, não deveríamos apenas informar o usuário que não tem saldo para o débito e retornar, para evitar que a thread fique “travada” neste ponto, ou seja, esperando um crédito para realizar o débito e esse crédito talvez nunca ocorra?

Agora que estou lendo as outras mensagens vi que já tinha percebido o que eu falei :slight_smile:
Sacanagem desse professor, kkk

Eu fiquei com uma dúvida. No método debitar você espera o saldo ficar maior que zero. Neste exemplo específico não tem problema porque da forma que está o looping sempre alguma thread vai creditar em algum momento o valor necessário para o débito. Mas num código para qualquer quantia de créditos/débitos, não deveríamos apenas informar o usuário que não tem saldo para o débito e retornar, para evitar que a thread fique “travada” neste ponto, ou seja, esperando um crédito para realizar o débito e esse crédito talvez nunca ocorra?
[/quote]

Muito bem Rogerio. O que vc disse está certo, pela estrutura desse exercício em algum momento as threads terão crédito adicionado ao saldo. Fazendo com que a brincadeira continue até o fim do ‘for’.

Para o Pessoal que ainda não entendeu, experimente comentar a linha ‘this.creditar(3);’ do método ‘run()’, e rode o programa.
Vcs perceberão que entrará num deadlock.

Rogerio pelo que entendi na sua pergunta vc quer simular uma situação em que pode-se creditar/debitar qualquer quantia(sendo elas diferentes: quantia_debitar != quantia_creditar).

Se vc fizer isso a chance de ocorrer deadlock é quase sempre certa. Então para evitar esse deadlock vc pode deixar o método ‘debitar’ assim:

public synchronized void debitar(double valor) { if (saldo < valor) { System.out.println("Saldo insuficiente"); return; //Como o Rogerio sugeriu } saldo -= valor; }

descomente a linha ‘this.creditar(3);’ do método ‘run()’, retire todos os ‘notifyAll()’ do programa inteiro e faça um teste.
A msg “Saldo insuficiente” não deverá aparecer. certo?
Agora comente novamente e torne a executar.
A msg “saldo insuficiente” deve aparecer e o programa chegar ao fim, porém os saldos finais não são iguais.

Agora vamos entrar nos detalhes:

Usando o ‘return’ no método ‘debitar’ vc não impede que a thread continue executando, ou seja ela continua disputando ciclos de cpu com as outras threads.
Com o ‘wait()’ eu tiro a thread da fila ‘pronto’ do S.O. e coloco na fila ‘espera’. Sendo a mesma retirada desta fila quando uso o ‘notifyAll()’.
Assim eu disponibilizo ciclos de cpu para outros processos executando no meu S.O.
Mas isso só funcionará neste caso. Como dizem “cada caso é um caso”, cabe a nós analisarmos a situação.

Acho esse exercício meramente didático, na vida real é quase improvável ter um caso destes.(posso estar errado :roll: )

Conclusão:
Nesse caso o objetivo é que ao terminar o programa todas as threads tenham o mesmo valor de saldo. Portanto para garantir que as threads cheguem ao fim do programa com saldos iguais, eu uso o ‘wait’ ao invés de ‘return’, assim se uma delas não tiver nos conformes o programa não termina.(Claro que isso só acontecerá se alterarmos a lógica do programa: método run(),main() e etc);
Oia… vale dois certos, não acham?!

Bom, acho que respondi a pergunta. Se não era isso que vc queria saber, por favor tente ser mais claro.

Grato Pessoal.

Era isso mesmo. Valeu pela resposta. No caso do deadlock quando estava fazendo aqui ele ocorreu quando tentei sincronizar A e logo depois B no método transferir, por isso tive que mudar o código para garantir a ordem dos locks nos objetos, já que essa é uma das maneiras de se evitar o deadlock. Agora eu não sei direito qual é o objetivo do professor, se é ensinar como sincronizar o código para regiões críticas apenas, se é para sincronizar e evitar que deadlocks ocorram entre threads que competem pelo acesso a estas regiões críticas, ou se era apenas para resolver a pegadinha do malandro no enunciado :slight_smile:

Fiz um teste com esta sua “listagem completa”, colocando 5 no saldo inicial e no loop, não funfa! resultado:

Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
Saldo Insuficiente
C1: 5.0 C2: 11.0 C3: 8.0 C4: 17.0
Tempo: 2

eu acho q tem q funcionar com 1 milhão.

e o exemplo q postei funciona com 1 milhao sem problema.

Não, tem que funcionar com qualquer valor acima de 1.

Não, tem que funcionar com qualquer valor acima de 1.

[/quote]

se vc colocar 2.

a primeira thread dispara e antes q vc da o start na segunda

já vai dar saldo insuficiente. pq ela só debita nas outras.

Não, tem que funcionar com qualquer valor acima de 1.

[/quote]

se vc colocar 2.

a primeira thread dispara e antes q vc da o start na segunda

já vai dar saldo insuficiente. pq ela só debita nas outras.
[/quote]

Então Gilson,
Acho que a unica maneira de conseguir fazer com que funcione com qualquer número seria uma forma de travar por thread, ou seja, se uma thread entrar no run, ela teria que executar todo o codigo da classe antes de liberar para outra thread, agora… Como conseguir este tipo de “trava”?

mas ia q não iria funcionar mesmo.

imagine;

a primeira debitaria 1 milha de vezes na outra.

da pra testar facil

t1.start;
t1.join;
t2.start;
t2.join;

[quote=GilsonNunes]mas ia q não iria funcionar mesmo.

imagine;

a primeira debitaria 1 milha de vezes na outra.

da pra testar facil

t1.start;
t1.join;
t2.start;
t2.join;

…[/quote]

Não, nao foi isto que eu quis dizer, imagine que a primeira thread entrasse e fizesse o “trabalho dela” e saisse, depois uma outra e assim sucessivamente, ou seja, algo do tipo atomico, uma thread teria que executar todo o codigo para dar a vez para outra, mas mesmo assim não sei se funcionaria, pois um mesma thread poderia ganhar a “disputa” por mais vezes cque as outras e no fim ocorreriam inconsistencias de qualquer forma, se garantissemos que cada thread executasse de forma atomica o codigo e em uma sequencia pre-determinada, do tipo: t1, t2, t3, t4, acho atenderia, mas ai não vejo vantagem do uso de thread, sei lá… isto tá me parecendo “pegadinha”. :slight_smile: