Синхронизация по локальным переменным - PullRequest
28 голосов
/ 27 ноября 2009

У меня есть многопоточный код Java, в котором:

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

Я создал такой метод:

public void process(Foo[] foos) {
    for (final Foo foo : foos) {
        if (foo.needsProcessing()) {
            synchronized (foo) {
                foo.process();  // foo's state may be changed here
            }
        }
    }
}

Насколько мне известно, это выглядит законным. Однако инспекция IntelliJ жалуется на синхронизацию с локальной переменной, потому что «разные потоки могут иметь разные локальные экземпляры» (для меня это не , так как я не инициализирую foos в методе).

По сути, я хочу добиться того же, что и синхронизировать метод Foo.process () (для меня это не вариант, так как Foo является частью сторонней библиотеки).

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

Заранее спасибо!

Ответы [ 7 ]

22 голосов
/ 27 ноября 2009
if (foo.needsProcessing()) {
    synchronized (foo) {
        foo.process();  // foo's state may be changed here
    }
}

Я думаю, что в приведенном выше фрагменте есть состояние гонки, которое может привести к тому, что foo.process() иногда вызывается дважды для одного и того же объекта. Должно быть:

synchronized (foo) {
    if (foo.needsProcessing()) {
        foo.process();  // foo's state may be changed here
    }
}

Неужели это так плохо для синхронизации на местных жителей?

Неплохая синхронизация с местными жителями per se . Реальные проблемы:

  • синхронизируют ли разные потоки на правильных объектах для достижения правильной синхронизации, и

  • может ли что-то еще вызывать проблемы при синхронизации этих объектов.

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

Ответ Стивена С. имеет проблемы, он бессмысленно вводит много блокировок синхронизации, как ни странно, лучший способ отформатировать его:

    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, который не нуждается в обработке.

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

2 голосов
/ 27 ноября 2009

IDE должен помочь вам, если он допустил ошибку, вам не следует наклоняться и доставлять ей удовольствие.

Вы можете отключить эту проверку в IntelliJ. (Забавно, это единственная функция, включенная по умолчанию в разделе «Проблемы с потоками». Это самая распространенная ошибка, которую допускают люди?)

1 голос
/ 27 ноября 2009

Нет авто-интеллекта совершенен. Я думаю, что ваша идея синхронизации с локальной переменной вполне верна. Так что, возможно, сделайте это по-своему (что правильно) и предложите JetBrains, чтобы они настроили свой осмотр.

1 голос
/ 27 ноября 2009

Реорганизовать тело цикла в отдельный метод, принимая foo в качестве параметра.

0 голосов
/ 30 ноября 2009

В мире .NET объекты иногда несут свой объект блокировки как свойство.

synchronized (foo.getSyncRoot()) {
    if (foo.needsProcessing()) {
        foo.process();  // foo's state may be changed here
    }
}

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

0 голосов
/ 27 ноября 2009

В вашей синхронизации с объектом в коллекции нет ничего плохого. Вы можете попробовать заменить foreach на обычный цикл for:

for (int n = 0; n < Foos.length; n++) {
    Foo foo = Foos[n];

    if (null != foo && foo.needsProcessing()) {
        synchronized (foo) {
            foo.process();  // foo's state may be changed here
        }
    }
}

или даже (так что детектор не будет срабатывать при Foo foo):

for (int n = 0; n < foos.length; n++) {
    if (null != foos[n] && foos[n].needsProcessing()) {
        synchronized (foos[n]) {
            foos[n].process();  // foos[n]'s state may be changed here
        }
    }
}

Не использовать временное значение для предотвращения множественного числа foos[n] не рекомендуется, но если оно работает для предотвращения нежелательного предупреждения, вы можете жить с ним. Добавьте комментарий, почему этот код имеет отклоняющуюся форму. : -)

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