Синхронизированный кодовый блок - PullRequest
2 голосов
/ 08 октября 2011

Почему люди "синхронизируются" всего за 1 строку кода?Что там "синхронизировать"?

public final void addListener(Listener listener) {
  synchronized (listeners) {
    listeners.add(listener);
  }
}

РЕДАКТИРОВАТЬ: Спасибо всем.Очень хорошие ответы на все!

Ответы [ 5 ]

8 голосов
/ 08 октября 2011

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

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

В этом конкретном случае вызов функции состоит из добавления значения в коллекцию listeners. Хотя создание потоково-безопасных коллекций не является невозможным, большинство коллекций не являются поточно-ориентированными для нескольких авторов Таким образом, чтобы гарантировать, что коллекция не испортилась, необходимо synchronized.

РЕДАКТИРОВАТЬ: Чтобы дать пример того, как все может запутаться, предположим, что это упрощенная реализация add, где length - это количество элементов в массиве items:

public void Add(T item) {
  items[length++] = item;
}

Этот length++ бит не является атомарным; он состоит из чтения, приращения и записи, и поток может быть прерван после любого из них. Итак, давайте перепишем это немного, чтобы увидеть, что на самом деле происходит:

public void Add(T item) {
  int temp = length;
  length = length + 1;
  items[temp] = item;
}

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

T1: int temp = length;
T2: int temp = length;
T2: length = length + 1;
T2: items[temp] = item;
T1: length = length + 1;
T1: items[temp] = item;

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

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

public void Add(T item) {
  items[++length] = item;
}

Опять переписываем это:

public void Add(T item) {
  length = length + 1;
  items[length] = item;
}

Теперь это возможная последовательность событий:

T1: length = length + 1;
T2: length = length + 1;
T2: items[length] = item;
T1: items[length] = item;

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

3 голосов
/ 08 октября 2011

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

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

1 голос
/ 08 октября 2011

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

Предположим, у вас был другой метод в классе, который содержал addListener:

public void removeListener(Listener listener) {
   synchronized (listeners) {
       listeners.remove(listener);
   }
}

Если поток T заблокировал объект слушателей при вызове addListener, то поток S должен был бы ждать снаружи синхронизированного блока, пока потокT снимает блокировку с объекта слушателя.Затем он получит блокировку, войдет в синхронизированный блок и вызовет listeners.remove (listener).

Однако код, который непосредственно обращается к объекту слушателей, не будет ожидать получения блокировки.

public void unsafeRemoveListener(Listener listener) {
   listeners.remove(listener);
}
1 голос
/ 08 октября 2011

стандартный пример:

count++;

это закулисное расширение до

int tmp=count;
tmp=tmp+1;
count=tmp;

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

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

0 голосов
/ 08 октября 2011

Таким образом, вы не столкнетесь с конфликтами, так как несколько потоков проходят через приложение, как очевидный ответ.С Ajax или Swing вы также хотите убедиться, что правильный слушатель имеет правильный объект, который он слушает.

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

Я еще не сделал Android, но я уверен, что концепция похожа.Получение неправильного объекта для слушателя является проблемой.

HTH.

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