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

10 respostas
G

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. =)

10 Respostas

G

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.

G

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

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

juno.rr

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.

L

poh que merda foi que esse maluko fez Overnight

olha ai !!

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;                                                        
    }                                                                                       
}

Versão com blocos synchronized

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;   
    }   
}
ericogr

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.

ViniGodoy

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.

L

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.

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);
			}
		}
	}
}
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);
			}
		}
	}
}
ViniGodoy

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.

L

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

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);
		}
	}
}
ViniGodoy

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

Criado 18 de outubro de 2010
Ultima resposta 19 de out. de 2010
Respostas 10
Participantes 6