Se eu sincronizei dois métodos na mesma classe, eles podem ser executados simultaneamente?

164

Se eu sincronizei dois métodos na mesma classe, eles podem ser executados simultaneamente no mesmo objeto ? por exemplo:

class A {
    public synchronized void methodA() {
        //method A
    }

    public synchronized void methodB() {
        // method B
    }
}

Eu sei que não posso executar methodA()duas vezes no mesmo objeto em dois segmentos diferentes. mesma coisa methodB().

Mas posso executar methodB()em threads diferentes enquanto methodA()ainda estiver em execução? (mesmo objeto)

Shelef
fonte

Respostas:

148

Ambos os métodos bloqueiam o mesmo monitor. Portanto, você não pode executá-los simultaneamente no mesmo objeto a partir de threads diferentes (um dos dois métodos será bloqueado até que o outro termine).

NPE
fonte
1
Eu tinha um complemento para esta pergunta. Suponha que ambos os métodos sejam estáticos agora methodA é chamado usando Class enquanto methodB é chamado usando objetos como A.methodA () em t1 e obj.methodB () em t2. O que vai acontecer agora, eles vão bloquear ????
AMOD
2
@ amod0017: obj.methodB()é sinônimo de A.methodB()quando methodB()é static. Portanto, sim, eles irão bloquear (no monitor da classe, não no objeto).
NPE 21/03
vai tentar voltar a isso. :)
amod 21/03
@NPE Portanto, mesmo que ambos os métodos sejam estáticos e 2 threads t1 e t2 no mesmo objeto tentem chamar methodA () e methodB () simultaneamente, apenas 1 (digamos t1) será executado e o outro thread terá que esperar até que t1 libere o bloqueio ?
Sreeprasad
8
Lembre-se de que métodos estáticos usam bloqueio no .classobjeto. Então, se você tiver class A {static synchronized void m() {} }. E então um thread chama new A().m()que adquire bloqueio no new A()objeto. Se, em seguida, outro segmento chama A.m()-lo ENTRA NO MÉTODO NO PROBLEMA porque o que ele procura é fechamento em A.classobjeto enquanto há segmentos possuem esse tipo de bloqueio . Portanto, mesmo que você tenha declarado o método, synchronizedele é acessado por dois threads diferentes AO MESMO TEMPO . Assim: nunca use referências de objeto para chamar métodos estáticos
Alex Semeniuk
113

No exemplo methodA e methodB são métodos de instância (em oposição a métodos estáticos). Colocar synchronizedum método de instância significa que o encadeamento precisa adquirir o bloqueio (o "bloqueio intrínseco") na instância do objeto em que o método é chamado antes que o encadeamento possa começar a executar qualquer código nesse método.

Se você tiver dois métodos de instância diferentes marcados como sincronizados e segmentos diferentes estiverem chamando esses métodos simultaneamente no mesmo objeto, esses segmentos estarão disputando o mesmo bloqueio. Depois que um thread obtém o bloqueio, todos os outros threads são excluídos de todos os métodos de instância sincronizados nesse objeto.

Para que os dois métodos sejam executados simultaneamente, eles precisam usar bloqueios diferentes, como este:

class A {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void methodA() {
        synchronized(lockA) {
            //method A
        }
    }

    public void methodB() {
        synchronized(lockB) {
            //method B
        }
    }
}

onde a sintaxe do bloco sincronizado permite especificar um objeto específico no qual o encadeamento em execução precisa adquirir o bloqueio intrínseco para entrar no bloco.

O importante é entender que, apesar de colocarmos uma palavra-chave "sincronizada" em métodos individuais, o conceito principal é o bloqueio intrínseco nos bastidores.

Aqui está como o tutorial Java descreve o relacionamento:

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.

O objetivo do bloqueio é proteger dados compartilhados. Você usaria bloqueios separados, como mostrado no código de exemplo acima, apenas se cada bloqueio protegesse diferentes membros de dados.

Nathan Hughes
fonte
Portanto, neste exemplo, o bloqueio está nos objetos lockA \ lockB e não na classe A? Este é um exemplo de bloqueio no nível de classe ?
Nimrod
2
@ Nimrod: está travando nos objetos lockA e lockB e não na instância de A. nada aqui está travando em uma classe. bloqueio no nível de classe significaria recebendo o bloqueio em um objeto de classe, usando algo parecido static synchronizedousynchronized (A.class)
Nathan Hughes
Aqui está o link para o tutorial em java, explicando exatamente o que é respondido aqui.
Alberto de Paola
18

O Java Thread adquire um bloqueio no nível do objeto quando entra em um método java sincronizado da instância e adquire um bloqueio no nível da classe quando entra no método java sincronizado estático .

No seu caso, os métodos (instância) são da mesma classe. Portanto, sempre que um thread entra no método ou bloco sincronizado com java, ele adquire um bloqueio (o objeto no qual o método é chamado). Portanto, outro método não pode ser chamado ao mesmo tempo no mesmo objeto até que o primeiro método seja concluído e o bloqueio (no objeto) seja liberado.

Srikanth
fonte
se eu tiver dois threads em duas instâncias diferentes da classe, eles poderão executar os dois métodos simultaneamente, de modo que um thread chame um método sincronizado e o outro chame o segundo método sincronizado. Se meu entendimento estiver correto, posso usar o private final Object lock = new object();sincronizado para permitir que apenas um thread possa executar um dos métodos? Obrigado
Yug Singh
13

No seu caso, você sincronizou dois métodos na mesma instância da classe. Portanto, esses dois métodos não podem ser executados simultaneamente em diferentes segmentos da mesma instância da classe A. Mas eles podem em diferentes instâncias da classe A.

class A {
    public synchronized void methodA() {
        //method A
    }
}

é o mesmo que:

class A {
    public void methodA() {
        synchronized(this){
            // code of method A
        }
    }
}
Oleksandr_DJ
fonte
e se eu definir um bloqueio como private final Object lock = new Object();e agora usar lockcom bloco sincronizado em dois métodos, sua declaração será verdadeira? IMO, pois Object é a classe pai de todos os objetos, portanto, mesmo se os threads estiverem em uma instância diferente da classe, apenas um poderá acessar o código dentro do bloco sincronizado por vez. Obrigado.
Yug Singh
Se você definir "bloqueio de objeto final privado" na classe e sincronizar com ele, preencherá uma instância de bloqueio por classe, para que ele se comporte da mesma forma que sincronizado (isso).
58578 Olimpandr_DJ #
Sim, Object é um pai de todas as classes, mas a instância "lock" no seu caso é "instância por classe proprietária", portanto, tem o mesmo efeito que "this" para sincronização.
58680 Olivier_DJ #
7

No link da documentação da oracle

A sincronização de métodos tem dois efeitos:

Primeiro, não é possível intercalar duas invocações de métodos sincronizados no mesmo objeto. 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

Isso responderá à sua pergunta: No mesmo objeto, você não pode chamar o segundo método sincronizado quando a execução do primeiro método sincronizado estiver em andamento.

Consulte esta página de documentação para entender os bloqueios intrínsecos e o comportamento dos bloqueios.

Aditya W
fonte
6

Pense no seu código como o código abaixo:

class A {

public void methodA() {
    synchronized(this){        
      //method A body
    }
}

public void methodB() {
    synchronized(this){
      // method B body
    }
}

Portanto, sincronizado no nível do método significa simplesmente sincronizado (isso). se algum encadeamento executar um método dessa classe, obteria o bloqueio antes de iniciar a execução e o manteria até a execução do método ser concluída.

Mas posso executar o methodB () em um thread diferente enquanto o methodA () ainda está em execução? (mesmo objeto)

Na verdade, não é possível!

Portanto, vários encadeamentos não poderão executar nenhum número de métodos sincronizados no mesmo objeto simultaneamente.

Khosro Makari
fonte
e se eu criar Threads em dois objetos diferentes da mesma classe? Nesse caso, se eu chamar um método de um thread e outro método do segundo thread, eles não estarão executando simultaneamente?
Yug Singh
2
Eles vão porque são objetos diferentes. Ou seja, se você deseja evitá-lo, pode usar métodos estáticos e sincronizar a classe ou usar um objeto de variável de classe como um bloqueio ou tornar a classe Singleton. @Yug Singh
Khosro Makari
4

Com toda a clareza, é possível que o método sincronizado estático e não estático possa ser executado simultaneamente ou simultaneamente, porque um está com bloqueio no nível do objeto e outro bloqueio no nível da classe.

pacmanfordinner
fonte
3

A idéia principal da sincronização, que não afunda facilmente, é que ela só terá efeito se os métodos forem chamados na mesma instância de objeto - ela já foi destacada nas respostas e comentários -

Abaixo, o programa de amostra deve identificar claramente o mesmo -

public class Test {

public synchronized void methodA(String currentObjectName) throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA in");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA out");
}

public synchronized void methodB(String currentObjectName)  throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB in");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB out");
}

public static void main(String[] args){
    Test object1 = new Test();
    Test object2 = new Test();
    //passing object instances to the runnable to make calls later
    TestRunner runner = new TestRunner(object1,object2);
    // you need to start atleast two threads to properly see the behaviour
    Thread thread1 = new Thread(runner);
    thread1.start();
    Thread thread2 = new Thread(runner);
    thread2.start();
}
}

class TestRunner implements Runnable {
Test object1;
Test object2;

public TestRunner(Test h1,Test h2) {
    this.object1 = h1;
    this.object2 = h2;
}

@Override
public void run() {
    synchronizedEffectiveAsMethodsCalledOnSameObject(object1);
    //noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(object1,object2);
}

// this method calls the method A and B with same object instance object1 hence simultaneous NOT possible
private void synchronizedEffectiveAsMethodsCalledOnSameObject(Test object1) {
    try {
        object1.methodA("object1");
        object1.methodB("object1");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

// this method calls the method A and B with different object instances object1 and object2 hence simultaneous IS possible
private void noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(Test object1,Test object2) {
    try {
        object1.methodA("object1");
        object2.methodB("object2");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
}

Observe a diferença na saída de como o acesso simultâneo é permitido conforme o esperado, se os métodos forem chamados em diferentes instâncias de objetos.

Ouput com noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () comentou - a saída está na ordem methodA in> methodA Out .. methodB in> methodB Out Saída com * noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () * comentado

e Ouput com synchronizedEffectiveAsMethodsCalledOnSameObject () comentado - a saída mostra o acesso simultâneo do método A pelo Thread1 e Thread0 na seção destacada -

Saída com * synchronizedEffectiveAsMethodsCalledOnSameObject () * comentado

Aumentar o número de threads tornará ainda mais visível.

somshivam
fonte
2

Não, não é possível, se fosse possível, ambos os métodos poderiam estar atualizando a mesma variável simultaneamente, o que poderia facilmente corromper os dados.

fastcodejava
fonte
2

Sim, eles podem executar simultaneamente os dois threads. Se você criar 2 objetos da classe, cada objeto conterá apenas um bloqueio e todo método sincronizado precisará de bloqueio. Portanto, se você deseja executar simultaneamente, crie dois objetos e tente executar usando a referência desses objetos.

coolts
fonte
1

Você está sincronizando no objeto que não está na classe. Então eles não podem rodar simultaneamente no mesmo objeto

xyz
fonte
0

Dois Threads diferentes que executam um método sincronizado comum no único objeto, já que o objeto é o mesmo, quando um thread o usa com o método sincronizado, ele deve varificar o bloqueio; se o bloqueio estiver ativado, esse segmento passará para o estado de espera, se o bloqueio estiver desativado, ele poderá acessar o objeto, enquanto o habilitará e trará o bloqueio somente quando a execução estiver concluída. quando os outros encadeamentos chegarem, ele modificará o bloqueio, uma vez que está ativado, esperará até que o primeiro encadeamento seja executado e libere o bloqueio colocado no objeto. Uma vez liberado o bloqueio, o segundo encadeamento terá acesso ao objeto e habilitará o bloqueio até sua execução. para que a execução não seja simultânea, os dois threads serão executados um a um,

Ankit yadav
fonte
1
Por favor pontue e coloque em maiúscula essa bagunça corretamente. Não existe uma palavra como 'varify'.
Marquês de Lorne