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

15 respostas
T

Given:

public class TestSeven extends Thread
{
	private static int x;

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

	public void run(){
		doThings();
	}
}

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.

15 Respostas

V

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:

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();
    }
}
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.

T

Olá ViniGodoy, vc está falando que isso:

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

é igual a isso:

(II)
void synchronized metodoQualquer(){

       //codigo da regiao critica aqui
}

???

Eu sempre achei que (II) fosse igual à:

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

:shock:

V

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.

T

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) ??

:?:

V

Sim, poderia.

R
ViniGodoy:
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.
Agora me bateu uma dúvida. o código abaixo:
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;
		}
	}
}
é igual a isso?
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;
	}

}
V

Sim, igual.

V

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.

T

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?

V

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.

R
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:

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();
    }
}
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.

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

V

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

R

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.

V

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

“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.

R

Obrigado, ta na lista das compras…

Criado 14 de janeiro de 2010
Ultima resposta 11 de mai. de 2012
Respostas 15
Participantes 4