Как синхронизировано работает в Java

Во-первых, пример:

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

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

Что именно блокирует «синхронизированный»? Одна и та же функция работает для одного и того же объекта (как я изначально думал)? Одна и та же функция для всех объектов одного класса? Все синхронизированные функции для одного и того же объекта? Все синхронизированные функции для всех объектов одного класса?

Помогите мне здесь.


person Daddy Warbox    schedule 14.04.2009    source источник
comment
Связанный пост - Что означает "синхронизированный"?   -  person RBT    schedule 08.09.2018


Ответы (4)


В Java каждый Object предоставляет потоку возможность synchronize или блокировки на нем. Когда метод синхронизирован, метод использует свой экземпляр объекта в качестве блокировки. В вашем примере методы bow и bowBack оба являются synchronized, и оба находятся в одном классе Friend. Это означает, что любой поток, выполняющий эти методы, будет синхронизироваться с экземпляром Friend в качестве его блокировки.

Последовательность событий, которая вызовет взаимоблокировку:

  1. Первый запущенный поток вызывает alphonse.bow(gaston), что соответствует synchronized объекту alphonse Friend. Это означает, что поток должен получить блокировку от этого объекта.
  2. Второй запущенный поток вызывает gaston.bow(alphonse), что соответствует synchronized объекту gaston Friend. Это означает, что поток должен получить блокировку от этого объекта.
  3. Первый запущенный поток теперь вызывает bowback и ожидает освобождения блокировки gaston.
  4. Второй запущенный поток теперь вызывает bowback и ожидает освобождения блокировки alphonse.

Чтобы показать последовательность событий более подробно:

  1. main() начинает выполняться в основном Therad (назовем его Thread #1), создавая два экземпляра Friend. Все идет нормально.
  2. Основной поток запускает свой первый новый поток (назовем его Thread #2) с кодом new Thread(new Runnable() { .... Поток № 2 вызывает alphonse.bow(gaston), что соответствует synchronized объекта alphonse Friend. Таким образом, поток № 2 получает «блокировку» для объекта alphonse и входит в метод bow.
  3. Здесь происходит временной интервал, и исходный поток получает возможность выполнить дополнительную обработку.
  4. Основной поток запускает второй новый поток (назовем его Thread #3), точно так же, как и первый. Поток №3 вызывает gaston.bow(alphonse), который синхронизируется с объектом gaston Friend. Поскольку никто еще не получил «блокировку» для экземпляра объекта gaston, поток № 3 успешно получает эту блокировку и входит в метод bow.
  5. Здесь происходит квант времени, и поток № 2 получает возможность выполнить дополнительную обработку.
  6. Поток № 2 теперь вызывает bower.bowBack(this);, где bower является ссылкой на экземпляр для gaston. Это логический эквивалент вызова gaston.bowBack(alphonse). Таким образом, этот метод synchronized в экземпляре gaston. Блокировка для этого объекта уже получена и удерживается другим потоком (поток № 3). Таким образом, поток № 2 должен ждать снятия блокировки на gaston. Поток помещается в состояние ожидания, что позволяет потоку № 3 выполняться дальше.
  7. Поток № 3 теперь вызывает bowback, что в данном случае логически совпадает с вызовом alphonse.bowBack(gaston). Для этого ему необходимо получить блокировку для экземпляра alphonse, но эта блокировка удерживается потоком № 2. Теперь этот поток находится в состоянии ожидания.

И теперь вы находитесь в положении, когда ни один поток не может быть выполнен. И поток № 2, и поток № 3 ожидают снятия блокировки. Но ни одна из блокировок не может быть снята без прогресса потока. Но ни один поток не может двигаться вперед без снятия блокировки.

Таким образом: Тупик!

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

person Eddie    schedule 14.04.2009
comment
Ох, хорошо. Таким образом, замок принадлежит всему объекту. Я не знаю, почему я думал, что это просто вызовы одного и того же синхронизированного метода для данного объекта, которые заблокированы. Думаю, это ответ на мой вопрос. - person Daddy Warbox; 15.04.2009
comment
Объект не выполняет синхронизацию/блокировку, а является потоком. - person Steve Kuo; 15.04.2009
comment
На самом деле, я специально говорил о том, что Thread получает блокировку, но я вижу, что некоторые мои формулировки неточны. Я сделаю это более ясным. - person Eddie; 15.04.2009

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

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

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

person 1800 INFORMATION    schedule 14.04.2009

Все синхронизированные функции для одного и того же объекта. Пометка метода как «синхронизированного» очень похожа на размещение блока «synchronized (this) {» вокруг всего содержимого метода. Причина, по которой я не говорю «идентичный», заключается в том, что я не знаю, выдает ли компилятор один и тот же байт-код или нет, но, насколько я знаю, определенный эффект времени выполнения одинаков.

Взаимная блокировка — это классическая запирающая инверсия. Одна нить блокирует альфонса. Затем (или одновременно в многоядерной системе) другой поток запирает гастон. Эта часть требует, чтобы планирование потоков просто чередовалось в нужных точках.

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

person Steve Jessop    schedule 14.04.2009

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

synchronized(this) {
  /// code here ...
}

блокировать.

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

В вашем случае взаимоблокировка происходит, когда Альфонс начинает прогибаться в потоке 1, таким образом входя в синхронизированный блок. Затем поток 1 заменяется системой, поэтому поток 2 может начаться, и Гастон поклонится. Но Гастон пока не может откланяться, потому что он синхронизируется на Альфонсе, а у Thread 1 уже есть этот замок. Таким образом, он будет ждать, пока поток 1 покинет этот блок. Затем система заменит поток 1 обратно, который попытается заставить Альфонса поклониться обратно. За исключением того, что он не может этого сделать, потому что Thread 2 имеет синхронизированную блокировку на Гастоне. Оба потока теперь застряли, ожидая, пока другой закончит поклон, прежде чем сможет поклониться в ответ...

person Varkhan    schedule 14.04.2009