Por que o synchronized não funciona neste código?

Olá amigos, estou com um problema no uso de threads. Quando temos informações compartilhados por threads é preciso tomar alguns cuidados para que não aconteçam resultados indesejáveis.

Neste meu código:

public class Teste
{
  public static void main( String argumentos[] )
  {
    PrimeThread p = new PrimeThread();
    PrimeThread q = new PrimeThread();
    p.start();
    q.start();
  }
}

class PrimeThread extends Thread
{
  public static int x = 5;
  public void run()
  {
    synchronized(this) {
    PrimeThread.x++;
    System.out.println(PrimeThread.x);
    PrimeThread.x--;
    System.out.println(PrimeThread.x);
    }
  }
}

Crio dois threads com o mesmo conteúdo e os executo. Quando um thread chega no synchronized(this) { ele deve executar todas as instruções deste bloco antes do outro thread continuar sua execução. Isso evita a intercalação de instruções e por consequência possíveis resultados indesejáveis.

A variável x inicia com o valor 5. O 1° primeiro thread a entrar no bloco synchronized(this) { acrescentaria 1 a essa variável, deixando ela com o valor 6. Este valor é impresso. Agora o valor da variável é decrementado, ficando 5. Novamente este valor é impresso. Somente após o término do bloco, o outro thread criado poderia continuar sua execução, entrar no seu bloco synchronized(this) { e repetir o mesmo processo do thread anterior.

O resultado da execução do programa deveria ficar assim:
6
5
6
5

Mas ele atinge com mais frequencia os resultados:
6
6
7
5

e

7
6
7
5

O que com certeza não é o esperado.

Será que o fato do meu processador possuir mais de um núcleo está causando esse problema? Se sim, como solucionar?

Desde já agradeço. =)

Eu testei este meu programa em um computador que possui apenas um processador, e este possuindo apenas um núcleo. Não acontecem resultados indesejáveis. Ou seja, esse meu problema com certeza tem haver com o meu processador de vários núcleos.

Existe alguma solução para isso?

Teria como limitar a execução dos dois threads a apenas um núcleo do processador?

Desde já agradeço.

Pesso aos responsáveis pelo fórum para punirem o Overnight pelo flood intenso.

Qualquer ajuda à minha dúvida será bem vinda! =)

O bloco synchronized funciona, o problema é que a jvm não garante a ordem de execução das threads.
Uma thread pode executar primeiro e mais vezes que outra.
Para isso funcionar vc teria que implementar um sistema de trava, em que uma thread só pode executar o método depois da outra.

poh que merda foi que esse maluko fez Overnight

olha ai !!

[code]class FilaCirc {
private final int TAM = 10;
private int vetInt[];
private int inicio, total;

public FilaCirc() {                                                                                                                                        
    vetInt=new int[TAM];                                           
    inicio=0;                                                               
    total =0;                                                              
}                                                                               
public synchronized void addElement(int v) throws Exception {                                                                                                           
    if (total == TAM) throw new Exception("Fila cheia!");                           
    vetInt[(inicio+total)%TAM] = v;                                                         
    total++;                                                                
}                                                                                   
public synchronized int getElement() throws Exception {                                        
        if (total == 0 ) throw new Exception("Fila vazia!");                           
        int temp = vetInt[inicio];                                                                                                                          
        inicio = (++inicio)%TAM;                                
        total--;                                                                   
        return temp;                                                        
}                                                                                       

}

					[/code]

Versão com blocos synchronized

[code]class FilaCirc {
private final int TAM = 10;
private int vetInt[];
private int inicio, total;

public FilaCirc() {   
    vetInt=new int[TAM];   
    inicio=0;   
    total =0;   
}   
public void addElement(int v) throws Exception {   
    synchronized(this) {   
        if (total == TAM) throw new Exception("Fila cheia!");   
        vetInt[(inicio+total)%TAM]=v;   
        total++;   
    }   
}   
public int getElement() throws Exception {   
    synchronized(this) {                
        if (total == 0 ) throw new Exception("Fila vazia!");   
        int temp = vetInt[inicio];   
        inicio = (++inicio)%TAM;   
        total--;   
    }   
    return temp;   
}   

} [/code]

tente isso:

public class TesteThread
{  
  public static void main( String argumentos[] )  
  {
  	for (int i = 0; i < 333; i++) {
		new PrimeThread().start();
	}
  }  
}  
  
class PrimeThread extends Thread  
{  
  private static PrimeThread obj = new PrimeThread();
  
  public static int x = 5;  
  
  public PrimeThread() {
  }
  
  public void run()  
  {
	synchronized(obj) {
		try {
			this.sleep(((int)Math.random() * 27));
			PrimeThread.x++;  
			System.out.println(PrimeThread.x);  
			
			this.sleep(((int)Math.random() * 27));
			PrimeThread.x--;  
			System.out.println(PrimeThread.x);  
		}
		catch (Exception ex){}
    }  
  }  
}  

Observe quem está no synchronized: um único obj ao invés da instancia…
Veja se funciona.

O synchronized funciona sobre a instância que está dentro dos parênteses.

Como você fez synchronized (this), e cada runnable é um objeto diferente, não haverá sincronização. Afinal, cada thread poderá obter um monitor diferente (o this), e entrar no trecho sincronizado.

O problema é que sua variável x é estática. E, portanto, ela é compartilhada entre todas as threads. Por isso, para que isso funcione, você deveria usar um objeto também estático no trecho synchronized. Assim, todas as threads se sincronizariam sobre o mesmo monitor. Foi o que o colega do post acima fez.

Esse exemplo demonstra por que objetos estáticos são ruins em trechos multi-threads. Primeiro, porque é fácil alguém utiliza-la fora do bloco sincronizado (afinal, ela é global). E segundo, por que a sincronização dela fatidicamente levará ao enfileiramento de todas as threads que a compatilhem. Isso é só mais um fator que soma aos vários problemas que variáveis estáticas tem.

Testa esse exemplo identico ao seu. O primeiro os numeros não estarão ordenados, já no segundo sim. Somente trocando o this pela classe.

[code]package br.com.guj.forum;

public class PrimeThread {

public static void main(String argumentos[]) {
	for (int i = 0; i < 10; i++) {
		new PT().start();
	}
}

static class PT extends Thread {

	public static int	x	= 5;

	@Override
	public void run() {
		synchronized (this) {
			PT.x++;
			System.out.println(PT.x);
			PT.x--;
			System.out.println(PT.x);
		}
	}
}

}[/code]

[code]package br.com.guj.forum;

public class PrimeThread {

public static void main(String argumentos[]) {
	for (int i = 0; i < 10; i++) {
		new PT().start();
	}
}

static class PT extends Thread {

	public static int	x	= 5;

	@Override
	public void run() {
		synchronized (PT.class) { // So mudou isso
			PT.x++;
			System.out.println(PT.x);
			PT.x--;
			System.out.println(PT.x);
		}
	}
}

}[/code]

Novamente, a explicacação do código do lsjunior é a mesma. O objeto PT.class também é um objeto estático e, portanto, compartilhado por todas as classes. Você poderia até usar ali String.class que também iria funcionar, afinal, é outro objeto estático que seria compartilhado por todas as threads daquele monitor.

Entretanto, a solução do ericogr é ainda melhor, pois o objeto que ele usou como monitor não poderia ser acidentalmente usado por outra classe, ou num contexto errado.

Uma outra forma seria sincronizar apenas o metodo que exibe e altera o valor da variavel, o codigo fica mais simples.

[code]
package br.com.guj.forum;

public class PrimeThread {

public static void main(String argumentos[]) {
	for (int i = 0; i < 10; i++) {
		new PT().start();
	}
}

static class PT extends Thread {

	public static int	x	= 5;

	@Override
	public void run() {
		this.print();
	}

	private synchronized void print() {
		PT.x++;
		System.out.println(PT.x);
		PT.x--;
		System.out.println(PT.x);
	}
}

}[/code]

E é exatamente equivalente ao método que ele mesmo postou, com a sincronização pela própria classe explícita.