Ответ Стивена С. имеет проблемы, он бессмысленно вводит много блокировок синхронизации, как ни странно, лучший способ отформатировать его:
public void process(Foo[] foos) {
for (final Foo foo : foos) {
if (foo.needsProcessing()) {
synchronized (foo) {
if (foo.needsProcessing()) {
foo.process(); // foo's state may be changed here
}
}
}
}
}
Получение блокировки синхронизации иногда может занять некоторое время, и если она удерживалась, что-то что-то меняло. Это может быть что-то, изменившее статус обработки foo в то время.
Вы не хотите ждать блокировки, если вам не нужно обрабатывать объект. И после того, как вы получите блокировку, она может не нуждаться в обработке. Поэтому, хотя это выглядит глупо, и начинающие программисты могут быть склонны убрать одну из проверок, но на самом деле это разумный способ сделать это, если функция foo.needsProcessing () является пренебрежимо малой.
Возвращаясь к основному вопросу, когда вы хотите синхронизировать данные на основе локальных значений в массиве, это потому, что время имеет решающее значение. И в этих случаях последнее, что вы хотите сделать, это заблокировать каждый элемент в массиве или обработать данные дважды. Вы будете синхронизировать локальные объекты только в том случае, если у вас есть несколько потоков, выполняющих кучу работы, и вам очень редко нужно касаться одних и тех же данных, но это вполне возможно.
Выполнение этого будет блокировать блокировку только тогда и только тогда, когда обработка этого foo, который требует обработки, вызовет ошибки параллелизма. В основном вы захотите использовать двукратный синтаксис в тех случаях, когда вы хотите синхронизировать только на основе точных объектов в массиве как таковых. Это предотвращает двойную обработку foos и блокирует любой foo, который не нуждается в обработке.
У вас остается очень редкий случай блокирования потока или даже ввода блокировки только и именно тогда, когда это имеет значение, а ваш поток блокируется только в тот момент, когда отсутствие блокировки приведет к ошибкам параллелизма.