Синхронизированный метод Java на объекте или методе?

Если у меня есть 2 синхронизированных метода в одном классе, но каждый из них обращается к различным переменным, может ли 2 потока одновременно обращаться к этим двум методам? Находит ли замок объект, или он становится таким же конкретным, как переменные внутри синхронизированного метода?

Пример:

class X {

    private int a;
    private int b;

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

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

}

Могут ли 2 потока одновременно обращаться к одному экземпляру класса X, выполняющему x.addA() и x.addB()?

Ответ 1

Если вы объявляете метод как синхронизированный (как вы делаете, вводя public synchronized void addA()), вы синхронизируете весь объект, поэтому два потока, обращающиеся к другой переменной из этого же объекта, в любом случае будут блокировать друг друга.

Если вы хотите синхронизировать только одну переменную за раз, чтобы два потока не блокировали друг друга при обращении к различным переменным, вы должны синхронизировать их по отдельности в блоках synchronized(). Если бы a и b были ссылками на объекты, вы бы использовали:

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

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

Но так как они примитивы, вы не можете этого сделать.

Я бы предложил вам вместо этого использовать AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}

Ответ 2

Синхронизированный по объявлению метода синтаксический сахар для этого:

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

Для статического метода для этого используется синтаксический сахар:

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

Я думаю, что если разработчики Java знали тогда, что теперь понимается в синхронизации, они не добавили бы синтаксический сахар, поскольку это чаще всего приводит к плохим реализациям concurrency.

Ответ 3

Из "Учебников Java ™" по синхронизированным методам:

Во-первых, невозможно выполнить два вызова синхронизированных методов для одного и того же объекта. Когда один поток выполняет синхронизированный метод для объекта, все другие потоки, которые вызывают синхронизированные методы для того же блока объекта (приостанавливают выполнение), пока первый поток не завершится с объектом.

Из "Учебников Java ™" по синхронизированным блокам:

Синхронизированные операторы также полезны для улучшения параллелизма с детальной синхронизацией. Предположим, например, что класс MsLunch имеет два поля экземпляра, c1 и c2, которые никогда не используются вместе. Все обновления этих полей должны быть синхронизированы, но нет причин препятствовать чередованию обновления c1 с обновлением c2 - и это уменьшает параллелизм, создавая ненужную блокировку. Вместо использования синхронизированных методов или иного использования блокировки, связанной с этим, мы создаем два объекта исключительно для обеспечения блокировки.

(Акцент мой)

Предположим, у вас есть 2 не перемежающиеся переменные. Таким образом, вы хотите получить доступ к каждому из разных потоков одновременно. Вы должны определить блокировку не для самого класса объекта, а для класса Object, как показано ниже (пример из второй ссылки 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++;
        }
    }
}

Ответ 4

Доступ к блокировке осуществляется на объекте, а не на методе. Какие переменные доступны в рамках метода, не имеет значения.

Добавление "синхронизированного" к методу означает, что поток, выполняющий код, должен получить блокировку объекта перед продолжением. Добавление "статической синхронизации" означает, что поток, выполняющий код, должен получить блокировку объекта класса перед продолжением. В качестве альтернативы вы можете обернуть код в блоке следующим образом:

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

чтобы вы могли указать объект, чья блокировка должна быть получена.

Если вы хотите избежать блокировки на содержащем объекте, вы можете выбрать между:

Ответ 5

Из документации oracle ссылка

Синхронизация синхронных методов имеет два эффекта:

Во-первых, невозможно, чтобы две вызовы синхронизированных методов на одном объекте чередовали. Когда один поток выполняет синхронизированный метод для объекта, все другие потоки, которые вызывают синхронизированные методы для одного и того же объекта (приостанавливать выполнение) до тех пор, пока первый поток не будет выполнен с объектом.

Во-вторых, когда синхронизированный метод завершается, он автоматически устанавливает связь между событиями и последующим вызовом синхронизированного метода для одного и того же объекта. Это гарантирует, что изменения состояния объекта будут видны для всех потоков

Взгляните на эту документацию страница, чтобы понять внутренние блокировки и поведение блокировки.

Это ответит на ваш вопрос: на том же объекте x вы не можете вызывать x.addA() и x.addB() в то же время, когда выполняется одно из выполнения синхронизированных методов.

Ответ 6

Вы можете сделать что-то вроде следующего. В этом случае вы используете блокировку a и b для синхронизации вместо блокировки на "this". Мы не можем использовать int, потому что примитивные значения не имеют блокировок, поэтому мы используем Integer.

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

Ответ 7

Если у вас есть какие-то методы, которые не синхронизированы и к которым относятся и изменяют переменные экземпляра. В вашем примере:

 private int a;
 private int b;

любое число потоков может обращаться к этим несинхронизированным методам одновременно, когда другой поток находится в синхронизированном методе одного и того же объекта и может вносить изменения в переменные экземпляра. Например, например: -

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

Вам нужно избегать сценария, при котором несинхронизированные методы обращаются к переменным экземпляра и изменяют его, иначе нет смысла использовать синхронизированные методы.

В приведенном ниже сценарии: -

class X {

        private int a;
        private int b;

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

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

Только один из потоков может быть либо в addA, либо в методе addB, но в то же время любое число потоков может вводить метод changeState. Ни один из двух потоков не может вводить addA и addB в одно и то же время (из-за блокировки уровня объекта), но в то же время любое количество потоков может входить в changeState.

Ответ 8

Этот пример (хотя и не очень красивый) может обеспечить более глубокое понимание механизма блокировки. Если incrementA синхронизирован, а incrementB не синхронизирован, то incrementB будет выполняться ASAP, но если incrementB также синхронизирован, тогда он должен " подождите ', чтобы incrementA закончил, прежде чем incrementB может выполнить свою работу.

Оба метода вызываются в один экземпляр - объект, в этом примере это: задание и "конкурирующие" потоки - это aThread и main.

Попробуйте " синхронизированный" в incrementB и без него, и вы увидите разные результаты. Если incrementB является " синхронизированным", то он должен ждать incrementA ( ) заканчивать. Выполняйте несколько раз каждый вариант.

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();
    }
}

Ответ 9

Да, он заблокирует другой метод, потому что синхронизированный метод применяется к объекту класса WHOLE, как указано... но в любом случае он заблокирует выполнение другого потока ТОЛЬКО при выполнении суммы в любом методе addA или addB, который входит, потому что когда он заканчивается... один поток будет БЕСПЛАТНО, а другой поток получит доступ к другому методу и т.д. отлично работает.

Я имею в виду, что "синхронизированный" выполняется именно для того, чтобы блокировать другой поток от доступа к другому при выполнении определенного кода. ТАК НАКОНЕЦ ДЕЙСТВИТЕЛЬНО ЭТОТ КОДИРОВАНО РАБОТАЕТ. [/P >

Как последнее замечание, если есть переменные 'a' и 'b', а не только уникальная переменная 'a' или какое-либо другое имя, нет необходимости синхронизировать эти методы, потому что это совершенно безопасно, (Другая ячейка памяти).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

Будет работать также

Ответ 10

Это может не сработать, так как бокс и автобоксинг от Integer к int и наоборот зависят от JVM, и существует высокая вероятность того, что два разных номера могут быть хэшированы по одному и тому же адресу, если они находятся между -128 и 127.

Ответ 11

В синхронизации Java, если поток хочет войти в метод синхронизации, он получит блокировку для всех синхронизированных методов этого объекта, а не только для одного синхронизированного метода, который использует поток. Таким образом, поток, выполняющий addA(), получит блокировку для addA() и addB(), поскольку оба синхронизированы. Так что другие потоки с тем же объектом не могут выполнять addB().