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:
[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:
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”.
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) {
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:
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.
Agora que estou lendo as outras mensagens vi que já tinha percebido o que eu falei
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
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.
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
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”?
[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”.