Подтвердить что ты не робот

Синхронизация по локальным переменным

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

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

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

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

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

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

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

Спасибо заранее!

4b9b3361

Ответ 1

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

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

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

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

Неплохо синхронизировать локальные жители как таковые. Реальные проблемы:

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

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

Ответ 2

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

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

Получение блокировки синхронизации иногда может занять некоторое время, и если оно было удержано, что-то что-то меняет. В то время это могло изменить что-то необходимое для обработки foo.

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


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

Выполнение этого только когда-либо ударит блокировку тогда и только тогда, когда обработка, которая нуждается в обработке, вызовет ошибки concurrency. В основном вы будете хотеть двойной стробированный синтаксис в тех случаях, когда вы хотите синхронизировать, основываясь только на точных объектах массива как таковых. Это предотвращает двойную обработку foos и блокировку любого foo, который не нуждается в обработке.

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

    if (null != foo && foo.needsProcessing()) {
        synchronized (foo) {
            foo.process();  // foo 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] state may be changed here
        }
    }
}

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

Ответ 7

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

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

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