Existe uma vantagem em usar um Método Sincronizado em vez de um Bloco Sincronizado?


401

Alguém pode me dizer a vantagem do método sincronizado sobre o bloco sincronizado com um exemplo?




11
@cletus esta questão é completamente diferente da stackoverflow.com/questions/442564/...
Yukio Fukuzawa

Respostas:


431

Alguém pode me dizer a vantagem do método sincronizado sobre o bloco sincronizado com um exemplo? Obrigado.

Não há uma clara vantagem de usar o método sincronizado sobre o bloco.

Talvez o único (mas eu não chamaria isso de vantagem) é que você não precisa incluir a referência do objeto this.

Método:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Quadra:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Vejo? Nenhuma vantagem.

Blocos que têm vantagens sobre os métodos, porém, principalmente na flexibilidade porque você pode usar outro objeto como bloqueio enquanto sincronizar o método seria bloquear todo o objeto.

Comparar:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

vs.

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Além disso, se o método crescer, você ainda poderá manter a seção sincronizada separada:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}

44
Um benefício para o consumidor da API é que o uso da palavra-chave sincronizada na declaração do método também declara explicitamente que o método é sincronizado na instância do objeto e é (presumivelmente) seguro para threads.
Scrubbie

59
Eu sei que essa é uma pergunta antiga, mas a sincronização em "this" é considerada em alguns círculos um antipadrão. A conseqüência não intencional é que, fora da classe, alguém pode bloquear uma referência de objeto igual a "this" e impedir que outros threads passem pelas barreiras da classe, criando potencialmente uma situação de conflito. Criando um "Objeto final privado = new Object ();" variável puramente para fins de travamento é a solução frequentemente usada. Aqui está outra pergunta relacionada diretamente a esse problema.
Justin.hughey #

30
"enquanto a sincronização do método trava a classe completa." Isso não está correto. Não bloqueia a classe completa, mas a instância completa. Vários objetos da mesma classe mantêm seu próprio bloqueio. :) Cumprimenta
codepleb 22/09

4
Algo interessante sobre isso é que o uso de um método sincronizado fará com que o bytecode gerado tenha 1 instrução a menos, uma vez que os métodos têm um bit sincronizado inserido em sua assinatura. Como o comprimento do bytecode é um fator de alinhamento de um método, mover o bloco para a assinatura do método pode ser a diferença na decisão. Em teoria, de qualquer maneira. Eu não basearia uma decisão de design em uma única instrução de bytecode salva, o que parece uma péssima idéia. Mas ainda assim, é uma diferença. =)
corsiKa

2
@ coriKa: você salva mais de uma instrução. Um synchronizedbloco é implementado usando duas instruções monitorentere monitorexit, além de um manipulador de exceções que garante que isso monitorexitseja chamado mesmo no caso excepcional. Tudo isso é salvo ao usar um synchronizedmétodo.
Holger

139

A única diferença real é que um bloco sincronizado pode escolher em qual objeto ele será sincronizado. Um método sincronizado pode usar apenas 'this'(ou a instância de Class correspondente para um método de classe sincronizado). Por exemplo, estes são semanticamente equivalentes:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

O último é mais flexível, pois pode competir pelo bloqueio associado de qualquer objeto, geralmente uma variável de membro. Também é mais granular porque você pode ter código simultâneo executando antes e depois do bloco, mas ainda dentro do método. Obviamente, você poderia facilmente usar um método sincronizado refatorando o código simultâneo em métodos não sincronizados separados. Use o que tornar o código mais compreensível.


O último também pode ter mérito se nem todo o código em foo () precisar ser sincronizado.
Evan

11
Isso é verdade, mas não o que "Warrior" perguntou: "A vantagem do método sincronizado" não existe.
OscarRyz 22/02/09

76

Método Sincronizado

Prós:

  • Seu IDE pode indicar os métodos sincronizados.
  • A sintaxe é mais compacta.
  • Força a divisão dos blocos sincronizados para separar métodos.

Contras:

  • Sincroniza com isso e, portanto, possibilita que pessoas de fora também sincronizem com ele.
  • É mais difícil mover o código para fora do bloco sincronizado.

Bloco sincronizado

Prós:

  • Permite usar uma variável privada para o bloqueio e forçar o bloqueio a permanecer dentro da classe.
  • Blocos sincronizados podem ser encontrados pesquisando referências à variável.

Contras:

  • A sintaxe é mais complicada e dificulta a leitura do código.

Pessoalmente, prefiro usar métodos sincronizados com classes focadas apenas na coisa que precisa de sincronização. Essa classe deve ser a menor possível e, portanto, deve ser fácil revisar a sincronização. Outros não precisam se preocupar com a sincronização.


Quando você diz "fique dentro da classe", você quer dizer "fique dentro do objeto ", ou estou perdendo alguma coisa?
OldPeculier

36

A principal diferença é que, se você usar um bloco sincronizado, poderá bloquear um objeto que não seja o que permite ser muito mais flexível.

Suponha que você tenha uma fila de mensagens e vários produtores e consumidores de mensagens. Não queremos que os produtores interfiram entre si, mas os consumidores devem poder recuperar mensagens sem ter que esperar pelos produtores. Então, nós apenas criamos um objeto

Object writeLock = new Object();

E a partir de agora, toda vez que um produtor quiser adicionar uma nova mensagem, apenas bloqueamos:

synchronized(writeLock){
  // do something
}

Portanto, os consumidores ainda podem ler e os produtores serão bloqueados.


2
Seu exemplo é limitado a leituras não destrutivas. Se a leitura remover a mensagem da fila, isso falhará se for feito em algum momento à medida que um produtor gravar na fila.
ceving 19/09/2013

30

Método sincronizado

Métodos sincronizados têm dois efeitos.
Primeiro, quando um encadeamento está executando um método sincronizado para um objeto, todos os outros encadeamentos que invocam métodos sincronizados para o mesmo bloco de objetos (suspender a execução) até que o primeiro encadeamento seja concluído com o objeto.

Segundo, quando um método sincronizado sai, ele estabelece automaticamente um relacionamento de antes do acontecimento com qualquer chamada subsequente de um método sincronizado para o mesmo objeto. Isso garante que as alterações no estado do objeto sejam visíveis para todos os threads.

Observe que os construtores não podem ser sincronizados - o uso da palavra-chave sincronizada com um construtor é um erro de sintaxe. Sincronizar construtores não faz sentido, porque apenas o encadeamento que cria um objeto deve ter acesso a ele enquanto está sendo construído.

Instrução Sincronizada

Diferentemente dos métodos sincronizados, as instruções sincronizadas devem especificar o objeto que fornece o bloqueio intrínseco: Na maioria das vezes, eu uso isso para sincronizar o acesso a uma lista ou mapa, mas não quero bloquear o acesso a todos os métodos do objeto.

P: Bloqueios intrínsecos e sincronização A sincronização é criada em torno de uma entidade interna conhecida como bloqueio intrínseco ou bloqueio do monitor. (A especificação da API geralmente se refere a essa entidade simplesmente como um "monitor".) Os bloqueios intrínsecos desempenham um papel em ambos os aspectos da sincronização: impor acesso exclusivo ao estado de um objeto e estabelecer relacionamentos anteriores ao passado, essenciais para a visibilidade.

Todo objeto tem um bloqueio intrínseco associado a ele. Por convenção, um encadeamento que precisa de acesso exclusivo e consistente aos campos de um objeto precisa adquirir o bloqueio intrínseco do objeto antes de acessá-los e liberar o bloqueio intrínseco quando terminar com eles. Diz-se que uma linha possui a trava intrínseca entre o momento em que ela adquiriu a trava e a liberou. Desde que um encadeamento possua um bloqueio intrínseco, nenhum outro encadeamento poderá adquirir o mesmo bloqueio. O outro encadeamento será bloqueado quando tentar obter o bloqueio.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Verifique as saídas diferentes com o método sincronizado, em bloco e sem sincronização.


10
+1 por ser o único até agora a mencionar que os construtores não podem ser sincronizados . Ou seja, em um construtor, você realmente tem apenas uma opção: Blocos sincronizados.
ef2011

Testei seu código como indicado, mas C é sempre 0, depois -2024260031 e a única coisa que o altera no código hash. Que comportamento deve ser visto?
perfil completo de Justin Johnson


29

Nota: métodos e blocos estáticos sincronizados funcionam no objeto Class.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}

18

Quando o compilador java converte seu código-fonte em código de bytes, ele lida com métodos e blocos sincronizados de maneira muito diferente.

Quando a JVM executa um método sincronizado, o encadeamento em execução identifica que a estrutura method_info do método possui o sinalizador ACC_SYNCHRONIZED definido e adquire automaticamente o bloqueio do objeto, chama o método e libera o bloqueio. Se ocorrer uma exceção, o encadeamento libera automaticamente o bloqueio.

A sincronização de um bloco de método, por outro lado, ignora o suporte interno da JVM para adquirir o manuseio de bloqueio e exceção de um objeto e requer que a funcionalidade seja explicitamente escrita em código de bytes. Se você ler o código de bytes de um método com um bloco sincronizado, verá mais de uma dúzia de operações adicionais para gerenciar essa funcionalidade.

Isso mostra as chamadas para gerar um método sincronizado e um bloco sincronizado:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

O synchronizedMethodGet()método gera o seguinte código de bytes:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

E aqui está o código de bytes do synchronizedBlockGet()método:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Uma diferença significativa entre o método sincronizado e o bloco é que, geralmente, o bloco sincronizado reduz o escopo do bloqueio. Como o escopo do bloqueio é inversamente proporcional ao desempenho, é sempre melhor bloquear apenas uma seção crítica do código. Um dos melhores exemplos de uso do bloco sincronizado é o bloqueio verificado duas vezes no padrão Singleton, onde, em vez de bloquear todo o getInstance()método, apenas bloqueamos a seção crítica do código que é usada para criar a instância Singleton. Isso melhora drasticamente o desempenho, porque o bloqueio é necessário apenas uma ou duas vezes.

Ao usar métodos sincronizados, você precisará ter cuidado extra se misturar métodos sincronizados estáticos e não estáticos.


11
Se olharmos para o método sincronizado de bytecode, bytecode é mais compacto e simples, por que não é mais rápido esse bloco sincronizado?
EatSleepCode

@eatSleepCode Observe que este é um bytecode que é "compilado" pela JVM. A JVM incluirá o necessário monitorentere monitorexitantes de executar o código.
Philip Couling

12

Na maioria das vezes eu uso isso para sincronizar o acesso a uma lista ou mapa, mas não quero bloquear o acesso a todos os métodos do objeto.

No código a seguir, um thread modificando a lista não bloqueará a espera de um thread que está modificando o mapa. Se os métodos foram sincronizados no objeto, cada método teria que esperar, mesmo que as modificações que estão fazendo não entrem em conflito.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}

7

Com blocos sincronizados, você pode ter vários sincronizadores, para que várias coisas simultâneas, mas não conflitantes, possam continuar ao mesmo tempo.


6

Métodos sincronizados podem ser verificados usando a API de reflexão. Isso pode ser útil para testar alguns contratos, como todos os métodos no modelo são sincronizados .

O seguinte fragmento imprime todos os métodos sincronizados do Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

Nota importante sobre o uso do bloco sincronizado: cuidado com o que você usa como objeto de bloqueio!

O trecho de código do usuário2277816 acima ilustra esse ponto em que uma referência a um literal de seqüência de caracteres é usada como objeto de bloqueio. Perceba que os literais de strings são automaticamente internados em Java e você deve começar a ver o problema: todo trecho de código que é sincronizado no "bloqueio" literal, compartilha o mesmo bloqueio! Isso pode facilmente levar a conflitos com trechos de código completamente não relacionados.

Não são apenas os objetos String que você precisa ter cuidado. As primitivas in a box também são um perigo, pois a caixa automática e os métodos valueOf podem reutilizar os mesmos objetos, dependendo do valor.

Para obter mais informações, consulte: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused


5

Geralmente, o uso de uma trava no nível do método é muito rude. Por que bloquear um código que não acessa nenhum recurso compartilhado bloqueando um método inteiro. Como cada objeto possui um bloqueio, é possível criar objetos fictícios para implementar a sincronização no nível do bloco. O nível do bloco é mais eficiente porque não bloqueia todo o método.

Aqui algum exemplo

Nível do método

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Nível do bloco

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Editar]

Para Collectiongostar Vectore Hashtableeles são sincronizados quando ArrayListou HashMapnão, e você precisa definir a palavra-chave sincronizada ou invocar o método sincronizado de Coleções:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list

5

A única diferença: os blocos sincronizados permitem o bloqueio granular, diferentemente do método sincronizado

Basicamente synchronized, métodos ou blocos foram usados ​​para escrever código seguro de thread, evitando erros de inconsistência de memória.

Esta questão é muito antiga e muitas coisas foram alteradas nos últimos 7 anos. Novas construções de programação foram introduzidas para segurança do encadeamento.

Você pode obter segurança de encadeamento usando API de simultaneidade avançada em vez de synchroniedblocos. Esta página de documentação fornece boas construções de programação para garantir a segurança do encadeamento.

Os objetos de bloqueio oferecem suporte a idiomas de bloqueio que simplificam muitos aplicativos simultâneos.

Os executivos definem uma API de alto nível para iniciar e gerenciar threads. As implementações de executores fornecidas pelo java.util.concurrent fornecem gerenciamento de conjunto de encadeamentos adequado para aplicativos de grande escala.

Coleções simultâneas facilitam o gerenciamento de grandes coleções de dados e podem reduzir bastante a necessidade de sincronização.

As variáveis ​​atômicas possuem recursos que minimizam a sincronização e ajudam a evitar erros de consistência de memória.

O ThreadLocalRandom (no JDK 7) fornece geração eficiente de números pseudo-aleatórios a partir de vários encadeamentos.

Melhor substituto para o sincronizado é o ReentrantLock , que usa LockAPI

Um bloqueio de exclusão mútua reentrante com o mesmo comportamento básico e semântica que o bloqueio implícito do monitor acessado usando métodos e instruções sincronizados, mas com recursos estendidos.

Exemplo com bloqueios:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Consulte java.util.concurrent e java.util.concurrent.atomic pacotes também para outras construções de programação.

Consulte também esta questão relacionada:

Sincronização vs Bloqueio


4

O método sincronizado é usado para bloquear todos os objetos. O bloco sincronizado é usado para bloquear objetos específicos.


3

Em geral, eles são basicamente os mesmos, exceto por serem explícitos sobre o monitor do objeto que está sendo usado versus o implícito desse objeto. Uma desvantagem dos métodos sincronizados que eu acho que às vezes é negligenciada é que, ao usar a referência "this" para sincronizar, você deixa em aberto a possibilidade de objetos externos travarem no mesmo objeto. Isso pode ser um bug muito sutil, se você o encontrar. A sincronização em um objeto explícito interno ou em outro campo existente pode evitar esse problema, encapsulando completamente a sincronização.


2

Como já foi dito aqui, o bloco sincronizado pode usar a variável definida pelo usuário como objeto de bloqueio, quando a função sincronizada usa apenas "this". E é claro que você pode manipular com áreas de sua função que devem ser sincronizadas. Mas todo mundo diz que não há diferença entre a função sincronizada e o bloco, que cobre toda a função usando "this" como objeto de bloqueio. Isso não é verdade, a diferença está no código de bytes que será gerado nas duas situações. No caso de uso sincronizado de blocos, deve ser alocada uma variável local que contém referência a "this". E, como resultado, teremos um tamanho um pouco maior para a função (não relevante se você tiver apenas um pequeno número de funções).

Explicação mais detalhada da diferença que você pode encontrar aqui: http://www.artima.com/insidejvm/ed2/threadsynchP.html


2

No caso de métodos sincronizados, o bloqueio será adquirido em um Objeto. Mas se você optar pelo bloco sincronizado, terá a opção de especificar um objeto no qual o bloqueio será adquirido.

Exemplo:

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

2

Sei que essa é uma pergunta antiga, mas com a rápida leitura das respostas aqui, não vi ninguém mencionar que, às vezes, um synchronizedmétodo pode ser o bloqueio errado .
Na Concorrência Java na prática (pág. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

O código acima parece ser seguro para threads. No entanto, na realidade não é. Nesse caso, o bloqueio é obtido na instância da classe. No entanto, é possível que a lista seja modificada por outro encadeamento que não use esse método. A abordagem correta seria usar

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

O código acima impediria que todos os threads que tentassem modificar a lista modificassem a lista até que o bloco sincronizado fosse concluído.


lendo este livro no momento ... estou me perguntando ... se essa lista era privada em vez de pública e só tinha o método putIfAbsent, sincronizado (isso) seria suficiente, certo? o problema em questão é porque a lista também pode ser modificada fora desse ListHelper?
dtc

@dtc sim, se a lista fosse privada e não vazasse em nenhum outro lugar da classe, isso seria suficiente, desde que você marque qualquer outro método na classe que modifique a lista como sincronizada também. No entanto, o bloqueio do método inteiro em vez de apenas o Listpode levar a problemas de desempenho se houver um registro de código que não necessariamente precisam ser sincronizados
aarbor

isso faz sentido. muito obrigado por responder! TBH, eu encontrei o livro bastante útil em expandir meu conhecimento e como abordar multithreading mas a sua também introduziu um novo mundo de confusão para mim
dtc

2

Por uma questão prática, a vantagem dos métodos sincronizados sobre os blocos sincronizados é que eles são mais resistentes a idiotas; porque você não pode escolher um objeto arbitrário para bloquear, não pode usar indevidamente a sintaxe do método sincronizado para fazer coisas estúpidas, como bloquear uma literal de string ou bloquear o conteúdo de um campo mutável que é alterado sob os threads.

Por outro lado, com métodos sincronizados, você não pode impedir que o bloqueio seja adquirido por qualquer thread que possa obter uma referência ao objeto.

Portanto, usar a sincronização como um modificador nos métodos é melhor para proteger seus cow-orkers de se machucarem, enquanto o uso de blocos sincronizados em conjunto com objetos de bloqueio final privados é melhor para proteger seu próprio código dos cow-orkers.


1

Em um resumo de especificação Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

A declaração sincronizada (§14.17) calcula uma referência a um objeto; ele tenta executar uma ação de bloqueio nesse objeto e não prossegue até que a ação de bloqueio seja concluída com êxito. ...

Um método sincronizado (§8.4.3.5) executa automaticamente uma ação de bloqueio quando é invocada; seu corpo não é executado até que a ação de bloqueio seja concluída com êxito. Se o método for um método de instância , ele bloqueia o bloqueio associado à instância para a qual foi invocado (ou seja, o objeto que será conhecido como esse durante a execução do corpo do método). Se o método for estático , ele bloqueia o bloqueio associado ao objeto Class que representa a classe na qual o método está definido. ...

Com base nessas descrições, eu diria que a maioria das respostas anteriores está correta, e um método sincronizado pode ser particularmente útil para métodos estáticos, onde você teria que descobrir como obter o "objeto de classe que representa a classe na qual o método estava" definiram."

Edit: Eu originalmente pensei que estas eram citações da especificação Java real. Esclareceu que esta página é apenas um resumo / explicação das especificações


1

TLDR; Não use o synchronizedmodificador nem a synchronized(this){...}expressão, mas synchronized(myLock){...}onde myLockexiste um campo de instância final contendo um objeto particular.


A diferença entre o uso do synchronizedmodificador na declaração do método e a synchronized(..){ }expressão no corpo do método é a seguinte:

  • O synchronizedmodificador especificado na assinatura do método
    1. é visível no JavaDoc gerado,
    2. é determinável programaticamente por meio de reflexão ao testar o modificador de um método para Modifier.SYNCHRONIZED ,
    3. exige menos digitação e recuo em comparação com synchronized(this) { .... }, e
    4. (dependendo do seu IDE) é visível no esboço da classe e na conclusão do código,
    5. usa o thisobjeto como bloqueio quando declarado em método não estático ou a classe envolvente quando declarado em método estático.
  • A synchronized(...){...}expressão permite que você
    1. sincronizar apenas a execução de partes do corpo de um método,
    2. para ser usado em um construtor ou em um bloco de inicialização ( estático ),
    3. para escolher o objeto de bloqueio que controla o acesso sincronizado.

No entanto, o uso do synchronizedmodificador ou synchronized(...) {...}com thiso objeto de bloqueio (como em synchronized(this) {...}) tem a mesma desvantagem. Ambos usam sua própria instância como o objeto de bloqueio para sincronização. Isso é perigoso porque não apenas o objeto em si, mas qualquer outro objeto / código externo que contenha uma referência a esse objeto também pode usá-lo como um bloqueio de sincronização com efeitos colaterais potencialmente graves (degradação do desempenho e conflitos ).

Portanto, a melhor prática é não usar o synchronizedmodificador nem a synchronized(...)expressão em conjunto com thiscomo objeto de bloqueio, mas um objeto de bloqueio privado para esse objeto. Por exemplo:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

Você também pode usar vários objetos de bloqueio, mas é necessário tomar cuidado especial para garantir que isso não resulte em conflitos quando usados ​​aninhados.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

1

Suponho que esta pergunta seja sobre a diferença entre o Thread Safe Singleton e a inicialização Lazy com bloqueio de verificação dupla . Sempre me refiro a este artigo quando preciso implementar algum singleton específico.

Bem, este é um Singleton seguro para threads :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Prós:

  1. Inicialização lenta é possível.

  2. É thread-safe.

Contras:

  1. O método getInstance () é sincronizado, causando um desempenho lento, pois vários threads não podem acessá-lo simultaneamente.

Esta é uma inicialização lenta com bloqueio de verificação dupla :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Prós:

  1. Inicialização lenta é possível.

  2. Também é thread-safe.

  3. O desempenho reduzido por causa da palavra-chave sincronizada é superado.

Contras:

  1. Primeira vez, isso pode afetar o desempenho.

  2. Como contras. O método de bloqueio de verificação dupla é suportável, portanto pode ser usado para aplicativos multithread de alto desempenho.

Consulte este artigo para obter mais detalhes:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/


-3

Sincronizando com threads. 1) NUNCA use sincronizado (isto) em um thread que não funcione. A sincronização com (this) usa o encadeamento atual como o objeto de encadeamento. Como cada segmento é independente de outros segmentos, NÃO há coordenação de sincronização. 2) Testes de código mostram que no Java 1.6 em um Mac a sincronização do método não funciona. 3) sincronizado (lockObj), em que lockObj é um objeto compartilhado comum de todos os threads sincronizados nele. 4) ReenterantLock.lock () e .unlock () funcionam. Veja os tutoriais de Java para isso.

O código a seguir mostra esses pontos. Ele também contém o vetor de thread-safe que seria substituído pelo ArrayList, para mostrar que muitos threads adicionados a um Vector não perdem nenhuma informação, enquanto o mesmo com um ArrayList pode perder informações. 0) O código atual mostra a perda de informações devido às condições da corrida. A) Comente a linha A atualmente rotulada e remova o comentário da linha A acima dela, depois execute, o método perde dados, mas não deve. B) Inverta a etapa A, remova o comentário B e // finalize o bloco}. Em seguida, execute para ver os resultados sem perda de dados. C) Comente B, descomente C. Execute, veja a sincronização em (isto) perde dados, conforme o esperado. Não tenha tempo para concluir todas as variações, espero que ajude. Se a sincronização estiver ativada (isto) ou a sincronização do método funcionar, indique qual versão do Java e do SO você testou. Obrigado.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class

11
Sincronizar com '(this)' faz o trabalho, e que não 'usar o atual segmento como o objeto de sincronização', a menos que o objeto atual é de uma classe que estende Thread. -1
Marquês de Lorne
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.