Dúvida em sincronismo de métodos e campos static

Given:

[code]public class TestSeven extends Thread
{
private static int x;

public synchronized void doThings(){
	int current = x;
	current++;
	x = current;
}

public void run(){
	doThings();
}

}[/code]

Which statement is true?

A. Compilation fails.
B. An exception is thrown in runtime.
C. Synchronizing the run() method would make the class thread-safe.
D. The data in variable “x” are protected from concurrent access problems.
E. Declaring the doThings() method as static would make the class thread-safe.
F. Wrapping the statements within doThings() in a synchronized(new Object()){} block would make the class thread-safe.

A resposta correta é a E. A minha pergunta é: ao fazer o método doThings() ficar estático, eu estou automaticamente sincronizando os campos estáticos também? Porque eu só concordo com essa resposta se a palavra synchronized no métod doThings() sincronizar também a variável static int x.

Esse exemplo mostra porque coisas synchronized são uma péssima escolha quando o assunto é thread safety.

O que acontece quando o trecho é sincronizado, mas não estático? Duas threads, rodando paralelamente, poderão acessar aquele trecho de código ao mesmo tempo, em objetos diferentes. Porém, ambas estarão compartilhando a variável x.

A solução para isso é que ambas as threads compartilhem o monitor. Para fazer isso, você declara o método como static (que fará com que compartilhem o monitor TesteSeven.class). Ou, no interior do método faz

public void doThings(){ synchronized (x) { int current = x; current++; x = current; } }
Para que ambas compartilhem x.

Uma última alternativa seria criar um lock externo, e fazer com que as threads o compartilhem:

[code]public class TestSeven extends Thread
{
private int[] lock;

private static int x;

public TestSeven(int[] lock) {
    this.lock = lock;
}

public void doThings(){
    synchronized (lock) {
        int current = x;
        current++;
        x = current;
    }
}

public void run(){
    doThings();
}

}[/code]

Isso seria usado assim:

int[] lock = new lock[0];
TestSeven one = new TestSeven(lock);
TestSeven two = new TestSeven(lock);

Agora, não entendi o que você quer dizer com “sincronize a variável x”. O que é sincronizado é a área de código, não variáveis. A sincronização faz com que duas threads não possam acessar o mesmo trecho de código do mesmo objeto ao mesmo tempo.

Olá ViniGodoy, vc está falando que isso:

(I)

void metodoQualquer(){ synchronized(NomeDaClasse.class){ //codigo da regiao critica aqui } }

é igual a isso:

(II)

[code]void synchronized metodoQualquer(){

   //codigo da regiao critica aqui

}[/code]

???

Eu sempre achei que (II) fosse igual à:

(III)

void metodoQualquer(){ synchronized(this){ //codigo da regiao critica aqui } }

:shock:

Apenas quando o método é estático. Aí vc não tem o this.

Quando o método não é estático, o sincronismo é feito em this. Então, se duas threads forem disparadas por objetos diferentes, o que acontece? Você tem dois “this” diferentes (cada um de um objeto), mas uma única variável x, já que ela é estática.

Humm, perfeito :smiley: Mais uma dúvida: eu poderia forçar o caso do código (I) ? Ou seja, eu poderia sincronizar um método não estático usando syncronized(NomeDaClasse.class) ??

:?:

Sim, poderia.

Agora me bateu uma dúvida. o código abaixo:

[code]public class Conta {
private double saldo;

public void retirar(double valor) {

	synchronized (this) {

		if (valor >= 0) {
			throw new RuntimeException("Valor inválido");
		}

		if (saldo - valor < 0) {
			throw new RuntimeException("Saldo insuficiente");
		}

		saldo += valor;
	}
}

public void depositar(double valor) {
	synchronized (this) {

		if (valor <= 0) {
			throw new RuntimeException("Valor inválido");
		}

		saldo += valor;
	}
}

}[/code]

é igual a isso?

[code]public class Conta {
private double saldo;

public synchronized void retirar(double valor) {

	if (valor >= 0) {
		throw new RuntimeException("Valor inválido");
	}

	if (saldo - valor < 0) {
		throw new RuntimeException("Saldo insuficiente");
	}

	saldo += valor;
}

public synchronized void depositar(double valor) {

	if (valor <= 0) {
		throw new RuntimeException("Valor inválido");
	}

	saldo += valor;
}

}[/code]

Sim, igual.

Note que embora essa seja a alternativa correta do ponto de vista da linguagem, é uma péssima alternativa do ponto de vista de multi-threading. Fazer o método doThings() sincronizado é o mesmo que dizer que apenas uma única thread pode percorre-lo, sempre, independente da classe do objeto.

Nesse caso, você literalmetne irá serializar todas as threads que precisarem do doThings(). E rodar uma thread por vez, ou não ter várias threads, é a mesma coisa.

Fiquei com outra dúvida agora. Caso (naquele primeiro código) a variável static x não fosse private, mas sim public, então não adiantaria nada colocar o método doThings() como static, correto? Pois aí, qualquer classe poderia fazer algo do tipo:

Podendo, assim, quebrar a consistência da váriável x.

Está correto o meu raciocínio?

Sim, está correto. Outra forma, seria se houvesse um método não-sincronizado acessando x. Qualquer classe que usasse esse método, estaria fazendo caquinha. A regra de ouro é:

Se existe duas ou mais threads alterando uma variável, todo e qualquer acesso a essa variável deve, necessariamente, estar num bloco sincronizado.

[quote=ViniGodoy]Esse exemplo mostra porque coisas synchronized são uma péssima escolha quando o assunto é thread safety.

O que acontece quando o trecho é sincronizado, mas não estático? Duas threads, rodando paralelamente, poderão acessar aquele trecho de código ao mesmo tempo, em objetos diferentes. Porém, ambas estarão compartilhando a variável x.

A solução para isso é que ambas as threads compartilhem o monitor. Para fazer isso, você declara o método como static (que fará com que compartilhem o monitor TesteSeven.class). Ou, no interior do método faz

public void doThings(){ synchronized (x) { int current = x; current++; x = current; } }
Para que ambas compartilhem x.

Uma última alternativa seria criar um lock externo, e fazer com que as threads o compartilhem:

[code]public class TestSeven extends Thread
{
private int[] lock;

private static int x;

public TestSeven(int[] lock) {
    this.lock = lock;
}

public synchronized void doThings(){
    int current = x;
    current++;
    x = current;
}

public void run(){
    doThings();
}

}[/code]

Isso seria usado assim:

int[] lock = new lock[0];
TestSeven one = new TestSeven(lock);
TestSeven two = new TestSeven(lock);

Agora, não entendi o que você quer dizer com “sincronize a variável x”. O que é sincronizado é a área de código, não variáveis. A sincronização faz com que duas threads não possam acessar o mesmo trecho de código do mesmo objeto ao mesmo tempo.[/quote]

Viny, não consegui enxergar o lock onde está acontecendo, poderia por favor me explicar… pois pensei que teria que em algum momento, passar o lock para synchronized.

Agradeço

A sincronização deveria ser em lock. Corrigi lá em cima.

Valeu Viny, por favor, você teria alguma referência de um bom livro(ou alguns) que tratasse somente de Threads? ja estudei semaforos, e outros assuntos…, e continuo com muita dificuldade, e vejo que você domina o assunto como eu nunca vi, qual foi o caminho para dominar de tal forma?

Obrigado.

Sim, o livro que indico no Roadmap:
http://pontov.com.br/site/java/47-javageral/89-roadmap-java

[quote=“Ponto V”]
Java Concurrency in Practice, do Brian Goetz: Um dos melhores livros sobre multi-threading e Java, escrito por ninguém menos do que o criador da linguagem.[/quote]

Obrigado, ta na lista das compras…