Bloco de codigo sincronizado

4 respostas
L

ae pessoal fiquei na duvida da implementacao do “bloco de codigo sincronizado” a kathy pede na pag 391 um exercicio a respeito porem nao conseguir entender a implementacao desse codigo ate q tentei mais n conseguir veja a sintaxe abaixo:

class Stin extends Thread{
	public void run(){
	for(int x=1;<10;x++){
	
	}

	}
}

class St{
	public static void main(String args[]){
		Stin t = new Stin();
		Stin t2 = new Stin();
		Stin t3 = new Stin();
		
		StringBuffer sb = new StringBuffer("A");

que puder me ajudar, a entender a ideia de bloco de codigo sincronizado agradeço… qdo chegou nessa parte eu fiquei com duvida… o bloco tem q ta dentro do metodo?.. pq as sincronizações é aplicada apenas ao metodo…

4 Respostas

V

Oi.

Imagine que você tem que fazer a seguinte operação:

public class ContaCorrente {
   public void saque(long valor) {
         int saldo = getSaldo();       //1
         saldo = saldo - valor;        //2
         setSaldo(saldo);              //3
    }
}

O programa parece, ok, não? Agora, imagine que duas Threads diferentes usem a mesma conta ao mesmo tempo, para fazer dois saques. Na nossa suposição a conta tem R$100,00 e o valor a sacar é R$10,00. Vamos ver a besteira que pode acontecer:

A primeira Thread executa 1;   int saldo = getSaldo(); //Saldo = R$100
A segunda Thread executa 1;   int saldo = getSaldo(); //Saldo = R$100
A primeira Thread executa 2;   saldo = saldo - valor; //Saldo = R$90,00
A primeira Thread executa 3;   setSaldo(saldo);       //O novo saldo é R$90,00
A segunda Thread executa 2;   saldo = saldo - valor  //Saldo = R$90,00
A segunda Thread executa 3;   setSaldo(saldo);       //O saldo final é R$90,00!!!

Ocorreram 2 saques, mas o seu programa aparentemente computou um... o gerente do banco não vai ficar nada feliz com o time do desenvolvimento... nada feliz mesmo.

Qual o problema aqui? Não podemos garantir em que ordem o processador vai executar as Threads. A situação acima poderia ser uma delas. Da mesma fora, a Thread 1 poderia executar integralmente, para só daí a Thread 2 executar e o programa pareceria ter funcionado.

Como resolvemos esse problema? Temos que garantir que a operação de saque seja atômica, ou seja, que seja impossível que duas Threads acessem ela ao mesmo tempo. Para isso, temos a palavra chave synchronized.

A sintaxe do synchronized é:
synchronized (objetoChave) {
   // Código sincronizado
}
Imagine o código sincronizado como uma porta desses banheiros de avião. Somente uma pessoa pode ficar lá dentro. Quando uma entrou, ela vira a chave (objetoChave) e todas as outras vêm a luz de "ocupado" do lado de fora e são obrigadas a esperar.

O código da conta corrigido ficaria assim:

public class ContaCorrente {
   int[] chave = new int[0]; //Criamos um objeto qualquer para servir de chave

   public void saque(long valor) {
         synchronized (chave) {
            int saldo = getSaldo();       //1
            saldo = saldo - valor;        //2
            setSaldo(saldo);              //3
         }
    }
}
Agora o que acontece? Se ocorre-se a mesma sequencia anterior teríamos:
A primeira Thread pega a chave e executa 1;   int saldo = getSaldo(); //Saldo = R$100
A segunda Thread tenta pegar a chave, mas não consegue. Passa então esperar a chave liberar;
A primeira Thread executa 2;   saldo = saldo - valor; //Saldo = R$90,00
A primeira Thread executa 3;   setSaldo(saldo);       //O novo saldo é R$90,00

A segunda Thread pega a chave e executa 1; int saldo = getSaldo(); //Saldo = R$90
A segunda Thread executa 2;   saldo = saldo - valor  //Saldo = R$80,00
A segunda Thread executa 3;   setSaldo(saldo);       //O saldo final é R$80,00.

Agora sim! Funcionou! Poderíamos usar essa chave para mais de um método. Por exemplo, o método deposito(int valor) também teria de ser sincronizado.

Mas... você pode estar se perguntando: e quando eu digo que um método é sincronizado? O que acontece?

O java usa o próprio objeto (this) como chave. Ou seja fazer:
public class ContaCorrente {
   public synchronized void saque(long valor) {
         int saldo = getSaldo();       //1
         saldo = saldo - valor;        //2
         setSaldo(saldo);              //3
    }
}
É a mesma coisa do que fazer:
public class ContaCorrente {
   public void saque(long valor) {
      synchronized (this) {
         int saldo = getSaldo();       //1
         saldo = saldo - valor;        //2
         setSaldo(saldo);              //3
      }
    }
}

É bom lembrar que a sincronização exige um custo. A chamada a um método sincronizado é centenas de vezes mais lenta do que a chamada a um método não sincronizado. Portanto, você deve usar sincronização apenas quando for necessário. Daí o conselho do Java quanto a classes como StringBuilder e StringBuffer, ArrayList e Vector, etc...

Outra coisa, a sincronização é um dos mecanismos, mas ela sozinha nem sempre garante que uma classe seja Thread Safe.

V

No Java 5, foi criada a classe Lock para fazer a sincronização.

Basicamente o código fica assim:

public class ContaCorrente {
    Lock chave = new ReentrantLock();

    public void saque(long valor) {
       chave.lock();
       try {
          int saldo = getSaldo();
          saldo = saldo - valor;
          setSaldo(saldo);
       } finally {
          chave.unlock();
       }
    }
 }

O uso dessas classes é opcional. A palavra chave synchronized ainda é válida e continua recomendada para sincronizações simples. A vantagem das classes é que você pode definir o tipo de lock e pode definir diferentes condições para waits. A desvantagem é que o código fica um pouco mais confuso, especialmente para programadores mais inexperientes com Java 5.

L

bloco de codigo eh meio estranho pq usa-lo? se ele faz o mesmo efeito que eu sincronizar o método?

V

Você pode usar a sincronização de um bloco por três motivos:

1. Para reduzir o custo da sincronização associado ao método (por exemplo, o método abaixo não sincroniza a validação de alguns campos). Cuidado para não usar isso em atributos compartilhados por várias Threads. Se várias Threads lêem, tem que ser sincronizado;
2. Para combinar mais de uma chave no momento da sincronização;
3. Para usar uma outra chave, diferente de this. Isso geralmente é uma boa prática.

Nada impede você te de ter:

private void metodo(Object parametro) {
   //Validação não sincronizada
   if (parametro == null)
      throw new IllegalArgumentException();

   //Parte duplamente sincronizada
   synchronized (chave1) {
       synchronized (this) {
             //Método com dupla sincronização
       }
   }
}

Para entender porque o terceiro item é uma boa prática, considere que seu usuário pode fazer:

public class OutraClasse {
    public ContaCorrente cc = new ContaCorrente();

    public void problemas() {
          synchronized (cc) {
                //Faz qualquer coisa
           }
    }
}

Olha só o problema... ContaCorrente usa this como chave na sincronização. Entretanto, a classe externa OutraClasse também está usando a variável cc (lembre-se QUALQUER objeto pode ser usado como chave). Dependendo da condição isso pode gerar problemas graves e difíceis de encontrar.

Nesse caso, quando alguém fizer um saque, o método problemas() também irá parar... Da mesma forma, se o método problemas() estiver sendo usado, ninguém usará os métodos sincronizados na ContaCorrente do atributo cc... estranho, não?

Criado 14 de janeiro de 2007
Ultima resposta 14 de jan. de 2007
Respostas 4
Participantes 2