Java синхронизирует метод блокировки объекта или метода? - PullRequest
170 голосов
/ 15 июня 2010

Если у меня есть 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() одновременно?

Ответы [ 11 ]

173 голосов
/ 15 июня 2010

Если вы объявите метод как synchronized (как вы делаете, набрав 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();
    }
}
60 голосов
/ 15 июня 2010

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

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

В статическом методе это синтаксический сахар для этого:

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

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

16 голосов
/ 09 января 2017

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

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

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

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

(выделение мое)

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

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

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

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

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

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

4 голосов
/ 21 февраля 2016

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

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

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

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

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

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

2 голосов
/ 07 августа 2015

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

 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.

2 голосов
/ 15 июня 2010

Вы можете сделать что-то вроде следующего. В этом случае вы используете синхронизацию 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++;
      }
   }
}
1 голос
/ 27 мая 2017

Да, он заблокирует другой метод, поскольку синхронизированный метод применяется к объекту класса WHOLE , как указано ... но в любом случае он заблокирует выполнение другого потока ONLY во время выполнения.сумма в любой метод addA или addB, который он вводит, потому что, когда он завершится ... один поток будет FREE объект, а другой поток получит доступ к другому методу и так далее отлично работает.

Я имею в виду, что "синхронизированный" создан именно для того, чтобы заблокировать другой поток от доступа к другому во время выполнения определенного кода.НАКОНЕЦ, ЭТОТ КОДЕКС БУДЕТ РАБОТАТЬ В КАЧЕСТВЕ.синхронизируйте эти методы, потому что это совершенно безопасно для доступа к другому var (Другая ячейка памяти).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

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

1 голос
/ 09 декабря 2016

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

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

Попробуйте с ' synchronized ' в incrementB и без него, и вы увидите другие результаты. Если incrementB is ' synchronized ', то нужно дождаться, пока 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();
    }
}
0 голосов
/ 20 мая 2019

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

...