Могут ли два потока одновременно обращаться к синхронизированному методу?

    public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

когда я запускаю эту программу, я получаю вывод как

Альфонс: Гастон поклонился мне! Гастон: Альфонс поклонился мне!

Итак, могут ли два потока одновременно обращаться к синхронизированному методу?

Ответ 1

Могут ли два потока обращаться к синхронизированному методу одновременно?

Это зависит от того, какой экземпляр объекта пытаются заблокировать два потока. Два потока не могут получить доступ к одному и тому же synchronized методу на одном и том же экземпляре объекта. Один получит блокировку, а другой заблокирует, пока первый поток не покинет метод.

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

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

  • Вы можете сделать метод static и synchronized в этом случае они будут блокировать сам объект класса. В загрузчике классов есть только один из этих объектов.

    public static synchronized void bow(Friend bower) {
    
  • Они оба могут заблокировать определенный статический объект. Что-то вроде:

    private static final Object lockObject = new Object();
    ...
    public void bow(Friend bower) {
        synchronized (lockObject) {
            ....
        }
    }
    
  • Или вы можете передать объект для блокировки, если не хотите делать его статичным.

Ваш вывод может быть что-то вроде следующего:

  1. gaston нить (может) начинается первой и вызывает bow(alphonse)
  2. это блокирует объект gaston и выводит: Gaston: Alphonse has bowed to me!
  3. он вызывает alphonse.bowBack(this).
  4. этот вызов блокирует объект alphonse и выводит: Alphonse: Gaston has bowed back to me!
  5. alphonse.bowBack(this) завершает работу, разблокируя объект alphonse.
  6. gaston.bow(alphonse) выходит, разблокируя объект gaston.
  7. тогда gaston нить выходит.
  8. alphonse нить (может) начинается следующим и вызывает bow(gaston)
  9. это блокирует объект alphonse и выводит: Alphonse: Gaston has bowed to me!
  10. он называет gaston.bowBack(this).
  11. этот вызов блокирует объект gaston и выводит: Gaston: Alphonse has bowed back to me!
  12. gaston.bowBack(this) выходит, разблокируя объект gaston.
  13. alphonse.bow(gaston) выходит, разблокируя объект alphonse.

Это может произойти в ряде разных заказов. Поток alphonse может запускаться первым, хотя метод start() alphonse позднее. Единственное, от чего вас спасают блокировки - это вызов alphonse.bow(...) если в alphonse.bowBack(...) момент запущен alphonse.bowBack(...). Как указал @user988052, поскольку каждый поток блокирует свой собственный объект, а затем пытается заблокировать другой, вы можете легко получить взаимоблокировку.

Ответ 2

Итак, могут ли два потока одновременно обращаться к синхронизированному методу?

Да и нет:

  • Да, если метод вызывается в разных экземплярах класса.

  • Нет, два потока не могут одновременно вызывать синхронные методы в одном экземпляре класса. Это так, даже если два потока вызывают разные методы (если экземпляр один и тот же).

Ответ 3

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

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

Создавайте потоки в цикле, и там очень высокая вероятность, что вы получите свой тупик:

for ( int i = 0; i < 1000; i++ ) {
    final Friend alphonse =
        new Friend("Alphonse");
    final Friend gaston =
        new Friend("Gaston");
    new Thread(new Runnable() {
        public void run() { alphonse.bow(gaston); }
    }).start();
    new Thread(new Runnable() {
        public void run() { gaston.bow(alphonse); }
    }).start();
}

Обратите внимание, что вы не будете блокировать свои потоки 2000: только некоторые из них будут заторможены. Вы можете проверить это, взяв файл вашей программы/JVM.

Ответ 4

С ключевым словом synchronized вы берете блокировку экземпляра для метода экземпляра или класса для статического метода. Таким образом, здесь вы гарантируете, что не более одной нити выполняет лук или лук на данном экземпляре в заданный момент времени (если один поток выполняет лук, ни один другой поток не может выполнять bowBack, потому что оба метода синхронизируются на одной и той же блокировке)...

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

Ответ 5

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

Когда срабатывает Deadlock, очень вероятно, что оба потока будут блокироваться при попытке вызвать bowBack. Ни один из блоков никогда не закончится, потому что каждый поток ждет другого, чтобы выйти из лука.