Dúvida - syncronized

Pessoal tenho o seguinte código abaixo:

[code]public class Conta {

private int saldo = 50;

public int getSaldo(){
	return saldo;
}

public void saque(int n){
	saldo -= n;
}

}

public class Teste implements Runnable {

private Conta c = new Conta();

public void run() {
	for (int i = 0; i < 5; i++) {
		sacar(10);
	}
}

public static void main(String[] args) {

	Teste t = new Teste();
	Thread one = new Thread(t);
	Thread two = new Thread(t);
	Thread three = new Thread(t);
	one.setName("Joao");
	two.setName("Maria");
	three.setName("Pedro");
	one.start();
	two.start();
	three.start();
	
}

private synchronized void sacar(int n) {
	
	if (c.getSaldo() >= n) {
		c.saque(n);
		System.out.println(Thread.currentThread().getName() + " sacou: "
				+ n + " reais " + "saldo em conta de: " + c.getSaldo()
				+ " reais");
		try {
			Thread.sleep(500);
		} catch (Exception e) {}
	} else {
		System.out.println("Sem saldo para o saque de "
				+ Thread.currentThread().getName() + " Saldo: "
				+ c.getSaldo() + " reais");
	}
}

}[/code]

No livro da Kathy fala que a palavra reservada syncronized funciona como um bloqueio, e quando o Thread é suspenso ele mantém o bloqueio para que outros Threads não possam utilizar aquele método marcado como syncronized.

Só que testando esse código acima eu consegui a seguinte saída:

Joao sacou: 10 reais saldo em conta de: 40 reais
Joao sacou: 10 reais saldo em conta de: 30 reais
Maria sacou: 10 reais saldo em conta de: 20 reais
Maria sacou: 10 reais saldo em conta de: 10 reais
Maria sacou: 10 reais saldo em conta de: 0 reais
Sem saldo para o saque de Pedro Saldo: 0 reais
Sem saldo para o saque de Pedro Saldo: 0 reais
Sem saldo para o saque de Pedro Saldo: 0 reais
Sem saldo para o saque de Pedro Saldo: 0 reais
Sem saldo para o saque de Pedro Saldo: 0 reais
Sem saldo para o saque de Maria Saldo: 0 reais
Sem saldo para o saque de Maria Saldo: 0 reais
Sem saldo para o saque de Joao Saldo: 0 reais
Sem saldo para o saque de Joao Saldo: 0 reais
Sem saldo para o saque de Joao Saldo: 0 reais

A Maria pulou a vez do João antes que ele terminasse de sacar todo o dinheiro, existe alguma coisa errada?

Abraço

Editado:
Será que quando o João sai da suspensão o método é liberado novamente e o agendador decide quem executa ele primeiro?

Fala ae David,

Também estou estudando para a prova, a parte de Threads é bem complicada, achei esse capítulo meio confuso no livro da Kathy.

Sobre o seu código, o método sincronizado é o sacar(), ou seja, ele só pode ser utilizado por um Thread por vez, mas isso não garante que o primeiro Thread vai fazer todas as chamadas a esse método antes de outro Thread chamá-lo, exatamente o que você comentou no fim da sua mensagem.

O pessoal do forum pode ajudar com mais dicas/explicações, creio para fazer o que você queria, você deve colocar o código do método run() como sincronizado, testei aqui e deu certo.

[quote=fsanchez]Fala ae David,

O pessoal do forum pode ajudar com mais dicas/explicações, creio para fazer o que você queria, você deve colocar o código do método run() como sincronizado, testei aqui e deu certo.

[/quote]

Kra funcionou colocando no método run(), mas será que essa é a melhor forma sincronizando o método run()? pq no livro o método run() não está sincronizado, pelo menos nos exemplos que eu vi até agora!

Valeu

Creio que não tem problema. Outra opção é colocar o código do método dentro de um bloco sincronizado:

	public  void run() {
		synchronized (this){
			for (int i = 0; i < 5; i++) {
					sacar(10);
			}
		}
	}

Mas no final o resultado é o mesmo.
Esse exemplo é mais para treinarmos para a prova, na prática não vejo muita aplicação para isso, já que tudo será executado como um Thread só, concorda? Já que o run() está sincronizado, e estamos trabalhando sobre a mesma instância da classe nas Threads, vai dar na mesma se você chamasse o método run() diretamente, 3 vezes. Um vai executar depois do outro.
A diferença é que não sabemos (da forma atual, com os Threads) qual será executado (apesar de ter grandes chances de seguir a ordem colocada no código).

Se você colocar no run, você irá forçar que um for rode depois do outro. Ou seja, vai serializar duas threads. Aí, pergunte-se, para que criar threads, se vou rodar um código depois do outro?

Obviamente, essa não é a melhor abordagem, caso você queira uma aplicação com execução paralela.

Olá.

Estou respondendo sua pergunta um pouco atrasado… mas é bom até para mim que tb estou estudando java, treinar as respostas.

O seu programa não roda na sequencia correta pq o agendador da maquina virtual java, não garante que
os programas rodarão em uma sequencia correta.

Não é possível prever qual será a regra pela qual o agendador, que as vezes é o agendador do sistema operacional
subjacente. O que o agendador pode garantir é que o seu thread rodará até concluir o método run(), porém sem uma
sequencia definida… a não ser que vc defina alguma regra no seu próprio software para isso

O código tá certo e a saída também

Funciona como bloqueio sim, imagina se 2 threads (Joao e Maria) entrassem no método ao mesmo tempo poderia haver uma inconsistência nos dados, é muito provável que acontecesse isto aqui:

Joao sacou: 10 reais saldo em conta de: 0 reais
Maria sacou: 10 reais saldo em conta de: -10 reais

[quote=dfsilva86][quote=fsanchez]Fala ae David,

O pessoal do forum pode ajudar com mais dicas/explicações, creio para fazer o que você queria, você deve colocar o código do método run() como sincronizado, testei aqui e deu certo.

[/quote]

Kra funcionou colocando no método run(), mas será que essa é a melhor forma sincronizando o método run()? pq no livro o método run() não está sincronizado, pelo menos nos exemplos que eu vi até agora!

Valeu[/quote]

na verdade não faz sentido algum vc sincronizar o método run…