Bloqueio de método sincronizado Java no object ou método?

Se eu tiver 2 methods sincronizados na mesma class, mas cada um acessando variables ​​diferentes, dois threads podem acessar esses dois methods ao mesmo tempo? O bloqueio ocorre no object ou é tão específico quanto as variables ​​dentro do método sincronizado?

Exemplo:

class X { private int a; private int b; public synchronized void addA(){ a++; } public synchronized void addB(){ b++; } } 

2 threads podem acessar a mesma instância da class X realizando x.addA( ) e x.addB() ao mesmo tempo?

   

    Se você declarar o método como synchonized (como você está digitando public synchronized void addA() ) você sincroniza em todo o object, então dois threads acessando uma variável diferente desse mesmo object bloqueariam um ao outro de qualquer maneira.

    Se você deseja sincronizar apenas em uma variável de cada vez, então dois threads não bloquearão um ao outro enquanto acessam variables ​​diferentes, você terá que sincronizá-los separadamente em blocos synchronized () . Se b fossem referências de objects, você usaria:

     public void addA() { synchronized( a ) { a++; } } public void addB() { synchronized( b ) { b++; } } 

    Mas desde que eles são primitivos você não pode fazer isso.

    Eu sugiro que você use AtomicInteger em vez disso:

     import java.util.concurrent.atomic.AtomicInteger; class X { AtomicInteger a; AtomicInteger b; public void addA(){ a.incrementAndGet(); } public void addB(){ b.incrementAndGet(); } } 

    Sincronizado na declaração do método é açúcar sintático para isso:

      public void addA() { synchronized (this) { a++; } } 

    Em um método estático, é um açúcar sintático para isso:

      ClassA { public static void addA() { synchronized(ClassA.class) { a++; } } 

    Eu acho que se os projetistas de Java soubessem o que é entendido agora sobre synchronization, eles não teriam adicionado o açúcar sintático, já que na maioria das vezes leva a implementações ruins de simultaneidade.

    O bloqueio acessado está no object, não no método. Quais variables ​​são acessadas dentro do método é irrelevante.

    Adicionar “sincronizado” ao método significa que o encadeamento que está executando o código deve adquirir o bloqueio no object antes de continuar. Adicionar “estático sincronizado” significa que o encadeamento que está executando o código deve adquirir o bloqueio no object de class antes de continuar. Alternativamente, você pode quebrar o código em um bloco como este:

     public void addA() { synchronized(this) { a++; } } 

    para que você possa especificar o object cujo bloqueio deve ser adquirido.

    Se você quiser evitar o bloqueio no object que o contém, você pode escolher entre:

    • Usando blocos sincronizados que especificam bloqueios diferentes
    • tornando ae b atômica (usando java.util.concurrent.atomic)

    Do essencial do Java SE em methods sincronizados :

    Primeiro, não é possível para duas invocações de methods sincronizados no mesmo object para intercalar. Quando um encadeamento está executando um método sincronizado para um object, todos os outros encadeamentos que invocam methods sincronizados para o mesmo bloco de object (suspendem execução) até que o primeiro encadeamento seja feito com o object.

    Do essencial do Java SE em blocos sincronizados :

    As instruções sincronizadas também são úteis para melhorar a simultaneidade com synchronization de baixa granularidade. Suponha, por exemplo, que a class MsLunch tenha dois campos de instância, c1 e c2, que nunca são usados ​​juntos. Todas as atualizações desses campos devem ser sincronizadas, mas não há razão para impedir que uma atualização de c1 seja intercalada com uma atualização de c2 – e isso reduz a simultaneidade criando bloqueios desnecessários. Em vez de usar methods sincronizados ou usar o bloqueio associado a isso, criamos dois objects apenas para fornecer bloqueios.

    (Ênfase minha)

    Você tem 2 variables ​​não intercaladas. Então você quer acessar cada um de diferentes segmentos ao mesmo tempo. você precisa definir o bloqueio não na class de object em si, mas na class Object como abaixo (exemplo do segundo link do Oracle):

     public class MsLunch { private long c1 = 0; private long c2 = 0; private Object lock1 = new Object(); private Object lock2 = new Object(); public void inc1() { synchronized(lock1) { c1++; } } public void inc2() { synchronized(lock2) { c2++; } } } 

    Do link da documentação do oracle

    Tornar methods sincronizados tem dois efeitos:

    Primeiro, não é possível para duas invocações de methods sincronizados no mesmo object para intercalar. Quando um encadeamento está executando um método sincronizado para um object, todos os outros encadeamentos que invocam methods sincronizados para o mesmo bloco de object (suspendem execução) até que o primeiro encadeamento seja feito com o object.

    Segundo, quando um método sincronizado é encerrado, ele estabelece automaticamente um relacionamento acontece antes de qualquer chamada subseqüente de um método sincronizado para o mesmo object. Isso garante que as alterações no estado do object sejam visíveis para todos os threads

    Dê uma olhada nesta página de documentação para entender os bloqueios intrínsecos e o comportamento de bloqueio.

    Isso responderá sua pergunta: No mesmo object x, você não pode chamar x.addA () e x.addB () ao mesmo tempo em que uma das execuções de methods sincronizados está em andamento.

    Você pode fazer algo parecido com o seguinte. Neste caso, você está usando o bloqueio em aeb para sincronizar em vez do bloqueio em “isto”. Não podemos usar int porque os valores primitivos não têm bloqueios, então usamos Integer.

     class x{ private Integer a; private Integer b; public void addA(){ synchronized(a) { a++; } } public synchronized void addB(){ synchronized(b) { b++; } } } 

    Se você tiver alguns methods que não estão sincronizados e estão acessando e alterando as variables ​​da instância. No seu exemplo:

      private int a; private int b; 

    qualquer número de encadeamentos pode acessar esses methods não sincronizados ao mesmo tempo em que outro encadeamento está no método sincronizado do mesmo object e pode fazer alterações em variables ​​de instâncias. Por exemplo: –

      public void changeState() { a++; b++; } 

    Você precisa evitar o cenário de que methods não sincronizados estão acessando as variables ​​de instância e alterando-as, caso contrário, não há nenhum ponto de usar methods sincronizados.

    No cenário abaixo: –

     class X { private int a; private int b; public synchronized void addA(){ a++; } public synchronized void addB(){ b++; } public void changeState() { a++; b++; } } 

    Apenas um dos encadeamentos pode estar no método addA ou addB, mas, ao mesmo tempo, qualquer número de encadeamentos pode entrar no método changeState. Não há dois encadeamentos que podem inserir addA e addB ao mesmo tempo (devido ao bloqueio de nível de object), mas, ao mesmo tempo, qualquer número de encadeamentos pode inserir changeState.

    Este exemplo (embora não seja bonito) pode fornecer mais informações sobre o mecanismo de bloqueio. Se incrementA é sincronizado , e incrementB não é sincronizado , então o incrementB será executado ASAP, mas se o incrementB também for sincronizado , ele terá que ‘esperar’ para que o incrementA termine, antes que o incrementB possa fazer seu trabalho.

    Os dois methods são chamados em instância única – object, neste exemplo, é: job e threads ‘concorrentes’ são aThread e main .

    Tente com ‘ synchronized ‘ em incrementB e sem ele e você verá resultados diferentes. Se incrementB também for ‘ synchronized ‘, então ele terá que esperar que incrementA () termine. Execute várias vezes cada variante.

     class LockTest implements Runnable { int a = 0; int b = 0; public synchronized void incrementA() { for (int i = 0; i < 100; i++) { this.a++; System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a); } } // Try with 'synchronized' and without it and you will see different results // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish // public void incrementB() { public synchronized void incrementB() { this.b++; System.out.println("*************** incrementB ********************"); System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b); System.out.println("*************** incrementB ********************"); } @Override public void run() { incrementA(); System.out.println("************ incrementA completed *************"); } } class LockTestMain { public static void main(String[] args) throws InterruptedException { LockTest job = new LockTest(); Thread aThread = new Thread(job); aThread.setName("aThread"); aThread.start(); Thread.sleep(1); System.out.println("*************** 'main' calling metod: incrementB **********************"); job.incrementB(); } } 

    Sim, ele irá bloquear o outro método porque o método sincronizado se aplica ao object de class INTEIRO como apontado … mas de qualquer forma ele bloqueará a execução do outro thread SOMENTE enquanto estiver executando a sum em qualquer método que addA ou addB entre, porque quando terminar … o único thread irá liberar o object e o outro thread acessará o outro método e assim por diante, funcionando perfeitamente.

    Quero dizer que o “sincronizado” é feito precisamente para bloquear o outro segmento de acessar outro enquanto em uma execução de código específico. Então, finalmente, este código funcionará bem.

    Como nota final, se houver uma variável ‘a’ e ‘b’, não apenas uma variável única ‘a’ ou qualquer outro nome, não há necessidade de sincronizar este método, pois é perfeitamente seguro acessar outro var. localização).

     class X { private int a; private int b; public void addA(){ a++; } public void addB(){ b++; }} 

    Vai funcionar também

    Isso pode não funcionar, já que o boxe e o autoboxing de Integer para int e viceversa dependem da JVM e há uma grande possibilidade de que dois números diferentes sejam colocados no mesmo endereço, se estiverem entre -128 e 127.