Thread - synchronized x Condition

Surgiu uma dúvida aqui sobre o uso de synchronized.

Com Lock, de Condition, eu consigo fazer com que ou um método seja acessado ou outro.

Por exemplo: Há duas threads. Uma querendo entrar em Ler_buffer() e outra thread querendo entrar em Escrever_buffer();
Aquela que pegar o lock primeiro, entra na região crítica. A outra thread, consequentemente, fica esperando a liberação do método.

Eu consegui fazer isso porque Ler() e Escrever() usavam o mesmo lock.]

Com synchronized eu apenas garanto que duas threads não entram ao mesmo tempo no mesmo método, certo?
Assim, uma thread pode entrar em Ler() e outra em Escrever().

Tá correto isso?

Não. Funciona da mesma forma. O synchronized atua sobre um objeto, que é o monitor. Se você não especificar o monitor esse objeto é this.

Caso você queira especificar monitores diferentes você faria:

[code]public class MinhaClasse{
private int[] monitor1 = new int[0]; //Um objeto qualquer, só pra servir de monitor
private int[] monitor2 = new int[0]; //Um objeto qualquer, só pra servir de monitor

public void escrever() {
synchronized(monitor1) {
//Trecho sincronizado
}
}

public void ler() {
synchronized(monitor2) {
//Trecho sincronizado
}
}
}[/code]

Um wait() dado num código sincronizado pelo monitor1 só será acordado por um notify() ou notifyAll() dado num trecho de código sincronizado pelo monitor1. Da mesma forma, uma thread pode percorrer o monitor 1 enquanto outra livremente anda pelo monitor 2.

Quando vc tem o synchronized na assinatura do método, é o mesmo que fazer:

public void metodo() { synchronized (this) { } }

E se o método for estatico é o mesmo que:

public static void metodoEstatico() { synchronized (MinhaClasse.class) { } }

E como o this será o mesmo para todos os métodos da classe, enquanto uma thread estiver num método sincronizado, outra não poderá acessar qualquer outro método.

Na verdade, não estou usando um monitor com um objeto, mas sim um método com synchronized.

public interface Buffer 
{
	public void set(int value);
	public int get();
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedBufferComSynchronized implements Buffer 
{
	private int buffer = -1;
	private boolean full;
	
	public synchronized void set(int value) 
	{
		while(full)
		{
			try
			{
				System.out.println("Produtor tenta produzir");
				displayState("Buffer cheio. Produtor espera.");
				
				wait();
			}
			
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		
		this.buffer = value;
		full = true;
		displayState("Produtor escreve "+this.buffer);
		notify();
	}
	
	public synchronized int get()
	{
		while(!full)
		{
			try
			{
				System.out.println("Consumidor tenta consumir");
				displayState("Buffer vazio. Consumidor espera.");
				wait();
			}
			
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		
		full = false;
		displayState("Consumidor consome "+this.buffer);
		notify();
		
		return this.buffer;
		
	}
	
	public void displayState(String mensagem)
	{
		System.out.printf("%-40s%d\t\t%b\n\n", mensagem, this.buffer, full);
	}
}
import java.util.Random;

public class Produtor implements Runnable 
{
	private static Random r = new Random();
	private Buffer sharedLocation;
	
	public Produtor(Buffer b)
	{
		sharedLocation = b;
	}

	public void run() 
	{
		int soma = 0;
		
		for(int i = 0; i <= 10; i++)
		{
			try
			{
				Thread.sleep(r.nextInt(2000));
				sharedLocation.set(i);
				soma+=i;
				System.out.printf("\t%sd\n", soma);
			}
			
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		
		System.out.printf("\n%s\n%s\n", "Produtor acaba de produzir", "Terminando produtor.");

	}

}
import java.util.Random;

public class Consumidor implements Runnable
{
	private static Random r = new Random();
	private Buffer b;
	private int value;
	
	public Consumidor(Buffer sharedBuffer)
	{
		b = sharedBuffer;
	}	
	
	public void run() 
	{
		int soma = 0;
		
		try
		{
			for(int i = 0; i <= 10; i++ )
			{
				Thread.sleep(2000);
				//value = b.get();
				soma += b.get();
				System.out.printf("\t\t\t%2d\n", soma);
			}
		}
		
		catch(InterruptedException e)
		{
			e.printStackTrace();
		}
		
		System.out.printf("\n%s\n%s\n", "Consumidor lê valores", soma, "Terminando consumidor.");

	}

}

O método set terá sempre apenas uma thread dentro. O mesmo para get.
Mas nada impede de ter uma thread dentro de get e outra em set, não é?

Essa é a dúvida.

Na main, faço somente…

public static void main(String []args)
	{
		ExecutorService application = Executors.newFixedThreadPool(2);
		
		Buffer b = new SynchronizedBufferComSynchronized();
		
		application.execute(new Produtor(b));
		application.execute(new Consumidor(b));
		
		application.shutdown();
	}

Eu pensei no seguinte.
Eu imagino que enquanto uma thread esetja em “set”, o sistema operacional pode parar e execução dessa, seja, por exemplo, porque o seu quantum terminou, e outra thread seja escalonada. Essa outra thread executa em “get”. Nada impede, no código acima, que a segunda thread entre em “get”.

Quando eu usava Condition, eu tinha esse controle…

Você chegou a ler o que escrevi? Você [color=red]sempre[/color] usa um objeto, mesmo que só o método seja declarado como synchronized.

Como eu já falei, se você deixar o método synchronized, a sincronização é feita sobre o objeto this. Ou seja se vc tem uma classe assim:

public class A { public synchronized void umMetodo() { //faz algo } }

É o mesmo que ter isso aqui:

public class A { public void umMetodo() { synchronized (this) { //faz algo } } }

Ou seja, um objeto criado assim:

A objetoA = new A(); objetoA.umMetodo();

Terá como monitor o “objetoA” (que é o this, nesse caso).

[quote=ECO2004]
Eu pensei no seguinte.
Eu imagino que enquanto uma thread esetja em “set”, o sistema operacional pode parar e execução dessa, seja, por exemplo, porque o seu quantum terminou, e outra thread seja escalonada. Essa outra thread executa em “get”. Nada impede, no código acima, que a segunda thread entre em “get”.

Quando eu usava Condition, eu tinha esse controle…[/quote]

Mas porque você pensou isso? A garantia que o synchronized dá é a mesma que o Lock dá: exclusão mútua. Se ele não desse essa garantia, qual seria a diferença entre um método sincronizado e um não sincronizado?
A única diferença é que com a classe Lock, você pode escolher entre diferentes estratégias de Locking, sendo a ReentrantLock (igual a do sinchronized) somente uma delas.

Você chegou a ler o que escrevi? Você [color=red]sempre[/color] usa um objeto, mesmo que só o método seja declarado como synchronized.

[/quote]

Li sim…

É que eu não sabia que ter um synchronized(objeto), seja objeto definido ou this e ter public void synchronized metodo() eram iguais.
Então, reafirmando…li sim o que vc escreveu.

Obrigado pela explicação!