Утверждение порядка синхронизации в Java

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

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

Существуют ли какие-либо легкие инструменты для инструментария кода Java, которые могут обнаруживать случаи, когда блокировки приобретаются в порядке, отличном от ожидаемого? Я в порядке с явным вызовом блокировки заказов через комментарии/аннотации.

Предпочтение отдается свободным и/или открытым источникам. Просьба также прокомментировать, если нет подходов к этой проблеме.

* Для моих целей легкие средства...

  • Если это инструментарий, я все равно могу запустить свою программу с той же базой производительности. Вероятно, деградация 30-50% приемлема.
  • Мне не нужно тратить половину дня на взаимодействие с инструментом, чтобы получить "все в порядке". В идеале я должен только заметить, что я использую его, когда есть проблема.
  • Если это инструмент, его следует легко отключить для производственных сред.
  • Он не должен загромождать мой код в каждом выражении synchronize. Как уже упоминалось ранее, я согласен с явным комментированием/аннотированием объектов или классов объектов, которые блокируются относительными упорядочениями.

Ответы

Ответ 1

Я не использовал AspectJ, поэтому не могу ручаться за то, как легко его использовать. Я использовал ASM, чтобы создать собственный профилировщик кода, это было около двух дней работы. Усилия по синхронизации инструмента должны быть схожими. AspectJ должен быть быстрее и легче, когда вы достигнете скорости с учетом аспектов.

Я реализовал отслеживание тупика для нашего сервера на базе С++. Вот как я это сделал:

  • Когда вы когда-либо приобретаете или отпускаете блокировку, я отслеживаю:
    • <time> <tid> <lockid> <acquiring|releasing> <location in code>
  • Этот дополнительный след повлиял на производительность довольно резко и не использовался в производстве.
  • Итак, когда был обнаружен возможный тупик, я использовал файл журнала, чтобы выяснить, что происходит вокруг тупика. Затем воспроизведите эту функциональность в тестовой среде с включенной трассировкой.
  • Затем я запустил script в файле журнала, чтобы узнать, существует ли взаимоблокировка и как. Я использовал awk script, используя этот алгоритм:
    • Линия
      • при приобретении
        • добавить lockid в список текущих блокировок для этого потока
        • добавьте каждую пару блокировок в этот список к наборам пар блокировки для этого потока. например, для списка Lock A -> Lock B -> Lock C генерируют пары (Lock A, Lock B), (Lock A, Lock C), (Lock B, Lock C)
      • при выпуске
        • удалить текущий lockid из хвоста списка для этого потока
    • Для каждой пары замков найдите все остальные потоки пар обратной блокировки, каждое совпадение является потенциальным тупиком, поэтому напечатайте пары и потоки, затронутые
    • Вместо того, чтобы сделать алгоритм более умным, я столкнулся с проверкой того, что захват блокировки, чтобы увидеть, был ли он реальным тупиком.

Я сделал это после того, как не смог найти причину тупика в течение нескольких дней, потребовалось еще несколько дней и несколько часов, чтобы найти тупик.

Если вы рассматриваете этот подход в Java, нужно рассмотреть следующие вопросы:

  • Используете ли вы только synchronized для защиты своих критических разделов? Используете ли вы классы в java.lang.concurrent? (для этого может потребоваться специальная обработка/контрольно-измерительная техника).
  • Насколько легко печатать местоположение кода с помощью аспектов /ASM? Я использовал __FILE__ и __LINE__ в С++. ASM предоставит вам имя класса, имя метода и подпись.
  • Вы не можете использовать блокировки, используемые для защиты трассировки/регистрации.
  • Вы можете оптимизировать свои инструменты, если вы используете файл журнала для каждого потока и создаете локальное хранилище для файлового объекта.
  • Как вы однозначно идентифицируете объекты, на которых вы синхронизируете? Может быть, toString() и System.identityHashCode() будет достаточно, но может потребоваться больше. Я использовал адрес объекта в С++.

Ответ 2

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

Ответ 3

Не добирается до вас весь путь, но хорошим началом является использование аннотаций JCIP и FindBugs улавливает несколько вещей.