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

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

ViniGodoy

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

:?:

ViniGodoy

Sim, poderia.

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

}
ViniGodoy

Sim, igual.

ViniGodoy

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?

ViniGodoy

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.

ribclauport
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

ViniGodoy

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

ribclauport

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.

ViniGodoy

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.

ribclauport

Obrigado, ta na lista das compras…

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