Pergunta

Em Java, a maneira idiomática de declarar seções críticas no código é o seguinte:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Quase todos os bloqueios sincronizam em this, mas há uma razão específica para isso? Existem outras possibilidades? Existem práticas recomendadas em que objeto sincroniza? (como instâncias privadas de Object?)

Foi útil?

Solução

Primeiro, observe que os seguintes trechos de código são idênticos.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

e:

public synchronized void foo() {
    // do something thread-safe
}

Faz exatamente a mesma coisa. Nenhuma preferência por qualquer um deles, exceto a legibilidade e o estilo de código.

Quando você sincroniza métodos ou blocos de código, é importante saber Por quê você está fazendo isso, e que objeto exatamente você está bloqueando e para qual propósito.

Observe também que existem situações em que você vai querer Sincronizar no lado do cliente blocos de código no qual o monitor você está pedindo (ou seja, o objeto sincronizado) não é necessariamente this, como neste exemplo:

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

Sugiro que você obtenha mais conhecimento sobre programação simultânea, isso servirá muito quando você souber exatamente o que está acontecendo nos bastidores. Você deve conferir Programação simultânea em Java, um ótimo livro sobre o assunto. Se você quiser um mergulho rápido no assunto, verifique Java concorrência @ sol

Outras dicas

Como os respondentes anteriores observaram, é uma prática recomendada sincronizar um objeto de escopo limitado (em outras palavras, escolha o escopo mais restritivo que você pode se safar e usar isso.) Em particular, sincronizando em this é uma má idéia, a menos que você pretenda permitir que os usuários da sua classe ganhem o bloqueio.

Um caso particularmente feio surge, no entanto, se você optar por sincronizar em um java.lang.String. As cordas podem ser (e na prática quase sempre estão) internadas. Isso significa que cada sequência de conteúdo igual - no JVM inteira - acaba sendo a mesma corda nos bastidores. Isso significa que, se você sincronizar em qualquer string, outra seção de código (completamente díspar) que também trava em uma string com o mesmo conteúdo, também bloqueará seu código.

Certa vez, eu estava solucionando um impasse em um sistema de produção e (muito dolorosamente) rastreei o impasse para dois pacotes de código aberto completamente díspares que cada um sincronizado em uma instância de string cujo conteúdo era ambos "LOCK".

Eu tento evitar sincronizar this Porque isso permitiria a todos de fora que tinham uma referência a esse objeto para bloquear minha sincronização. Em vez disso, crio um objeto de sincronização local:

public class Foo {
    private final Object syncObject = new Object();
    …
}

Agora posso usar esse objeto para sincronização sem medo de ninguém "roubar" a fechadura.

Apenas para destacar que também existem ReadWritelocks disponíveis em java, encontrados como java.util.concurrent.locks.readwritelock.

Na maior parte do meu uso, separo meu bloqueio como 'para ler' e 'para atualizações'. Se você simplesmente usar uma palavra -chave sincronizada, todas as leituras para o mesmo bloqueio de método/código serão 'na fila'. Apenas um thread pode acessar o bloco ao mesmo tempo.

Na maioria dos casos, você nunca precisa se preocupar com problemas de simultaneidade se estiver simplesmente fazendo lendo. É quando você está escrevendo que se preocupa com atualizações simultâneas (resultando em perdas de dados) ou leitura durante uma gravação (atualizações parciais), com as quais você precisa se preocupar.

Portanto, um bloqueio de leitura/gravação faz mais sentido para mim durante a programação multithread.

Você deseja sincronizar em um objeto que pode servir como um mutex. Se a instância atual (o isto A referência) é adequada (não um singleton, por exemplo), você pode usá -lo, como em java qualquer objeto pode servir como mutex.

Em outras ocasiões, convém compartilhar um mutex entre várias classes, se as instâncias dessas classes precisarem de acesso aos mesmos recursos.

Depende muito do ambiente em que você está trabalhando e do tipo de sistema que você está construindo. Na maioria dos aplicativos Java EE que eu já vi, na verdade não há necessidade real de sincronização ...

Pessoalmente, acho que as respostas que insistem que nunca são ou apenas raramente corretas para sincronizar this são equivocados. Eu acho que depende da sua API. Se sua classe é uma implementação do ThreadSafe e você a documenta, você deve usar this. Se a sincronização não for para tornar cada instância da classe como um ThreadSafe inteiro na invocação de seus métodos públicos, você deve usar um objeto interno privado. Componentes da biblioteca reutilizáveis muitas vezes Enrole -se na categoria anterior - você deve pensar com cuidado antes de proibir o usuário para envolver sua API em sincronização externa.

No primeiro caso, usando this permite que vários métodos sejam invocados de maneira atômica. Um exemplo é o PrintWriter, onde você pode querer produzir várias linhas (digamos um rastreamento de pilha para o console/logger) e garantir que elas apareçam juntas - neste caso, o fato de esconder o objeto Sync Internamente é uma dor real. Outro exemplo são os invólucros sincronizados da coleção - lá você deve sincronizar o próprio objeto de coleção para iterar; Como a iteração consiste em invocações de métodos múltiplos não podes proteja -o totalmente internamente.

Neste último caso, eu uso um objeto simples:

private Object mutex=new Object();

No entanto, tendo visto muitos dumps da JVM e traços de pilha que dizem que uma trava é "uma instância de java.lang.Object ()" Devo dizer que o uso de uma classe interna pode ser mais útil, como outros sugeriram.

De qualquer forma, esse é o meu dois bits.

Editar: uma outra coisa, ao sincronizar this Prefiro sincronizar os métodos e manter os métodos muito granulares. Eu acho que é mais claro e mais conciso.

A sincronização em Java geralmente envolve operações de sincronização na mesma instância. Sincronizando this então é muito idiomático desde this é uma referência compartilhada que está disponível automaticamente entre diferentes métodos de instância (ou seções de) em uma classe.

Usando outra referência especificamente para bloqueio, declarando e inicializando um campo privado Object lock = new Object() Por exemplo, é algo que eu nunca precisei ou usei. Eu acho que só é útil quando você precisa de sincronização externa em dois ou mais recursos não sincronizados dentro de um objeto, embora eu sempre tentasse refatorar essa situação em uma forma mais simples.

De qualquer forma, implícito (método sincronizado) ou explícito synchronized(this) é muito usado, também nas bibliotecas Java. É um bom idioma e, se aplicável, deve sempre ser sua primeira escolha.

O que você sincroniza depende de quais outros threads podem entrar em conflito com essa chamada de método podem sincronizar.

Se this é um objeto usado por apenas um tópico e estamos acessando um objeto mutável que é compartilhado entre threads, um bom candidato é sincronizar sobre esse objeto - sincronizando this não tem sentido, já que outro tópico que modifica esse objeto compartilhado pode nem saber this, mas conhece esse objeto.

Por outro lado, sincronizando this Faz sentido se muitos threads chamarem métodos desse objeto ao mesmo tempo, por exemplo, se estivermos em um singleton.

Observe que um método sincronizado geralmente não é a melhor opção, pois mantemos um bloqueio o tempo todo o método é executado. Se ele contiver peças seguras de cisão, mas threads, e uma parte não demorada que consome thread-insere, sincronizando o método está muito errado.

Quase todos os bloqueios sincronizam isso, mas há uma razão específica para isso? Existem outras possibilidades?

Esta declaração sincroniza todo o método.

private synchronized void doSomething() {

Esta declaração sincronizou uma parte do bloco de código em vez de todo o método.

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Da documentação do Oracle página

Fazer esses métodos sincronizados tem dois efeitos:

Primeiro, não é possível para duas invocações de métodos sincronizados no mesmo objeto para intercalar. Quando um thread está executando um método sincronizado para um objeto, todos os outros threads que invocam métodos sincronizados para o mesmo bloco de objeto (Suspender Execution) até que o primeiro thread seja feito com o objeto.

Existem outras possibilidades? Existem práticas recomendadas em que objeto sincroniza? (como instâncias privadas de objeto?)

Existem muitas possibilidades e alternativas à sincronização. Você pode tornar seu tópico de código seguro usando simultaneidade de alto nível APIs(Disponível desde o lançamento do JDK 1.5)

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

Consulte as perguntas abaixo de SE para obter mais detalhes:

Sincronização vs bloqueio

Evite sincronizado (isso) em Java?

As melhores práticas é criar um objeto apenas para fornecer a fechadura:

private final Object lock = new Object();

private void doSomething() {
  // thread-safe code
  synchronized(lock) {
    // thread-unsafe code
  }
  // thread-safe code
}

Ao fazer isso, você está seguro, que nenhum código de chamada pode ter um método de impasse por um não intencional synchronized(yourObject) linha.

(Créditos para @Jared e @Yuval-Adam, que explicaram isso em mais detalhes acima.)

Meu palpite é que a popularidade de usar this Nos tutoriais veio do início do Sun Javadoc: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top