Должен ли я всегда делать свой Java-код потокобезопасным или по соображениям производительности делать это только при необходимости? - PullRequest
23 голосов
/ 24 октября 2008

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

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

Если я выберу один из обоих способов, есть ли способы уменьшить недостатки? Или существует третья возможность, которую я должен использовать?

РЕДАКТИРОВАТЬ : Я привел причину, по которой этот вопрос возник у меня в голове. В нашей компании мы написали очень простое управление пользователями, которое записывает данные в файлы свойств. Я использовал его в веб-приложении, и после некоторой работы над ним я получил странные ошибки, из-за которых руководство пользователя забыло о свойствах пользователей (включая имя и пароль) и ролях. Это было очень раздражающим, но не всегда воспроизводимым, так что я думаю, что это было состояние гонки. Поскольку я синхронизировал все методы чтения и записи с / на диск, проблема исчезла. Так что я подумал, что, возможно, я мог бы избежать всех хлопот, если бы мы сначала написали класс с синхронизацией?

РЕДАКТИРОВАТЬ 2 : просматривая советы Pragmatic Programmer, я увидел совет № 41: Всегда разрабатывать для параллелизма. Это не говорит о том, что весь код должен быть потокобезопасным, но говорит, что дизайн должен учитывать параллелизм.

Ответы [ 13 ]

26 голосов
/ 24 октября 2008

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

В наши дни я пишу почти все, предполагая единую многопоточность, и помещаю знания о многопоточности в те немногие места, где это важно.

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

20 голосов
/ 25 октября 2008

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

По возможности используйте неизменяемые предметы. Сделайте атрибуты окончательными, установите их значения в конструкторах. Если вам нужно «изменить» данные, рассмотрите возможность возврата нового экземпляра. Неизменяемые объекты не нуждаются в блокировке.

Для объектов, которые не являются общими или ограниченными потоками, не тратьте время на их создание.

Документируйте ожидания в коде. Аннотации JCIP являются лучшим предопределенным выбором.

6 голосов
/ 24 октября 2008

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

Если явно не указано, что объект является потокобезопасным, ожидается, что это не так.

4 голосов
/ 24 октября 2008

Лично я бы проектировал только те классы, которые «поточнобезопасны» при необходимости - по принципу оптимизации только при необходимости. Похоже, что Sun пошла тем же путем с примером классов однопоточных коллекций.

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

  1. Самое важное: ДУМАЙТЕ, ЧТО ВЫ СИНХРОНИЗИРУЕТЕ. У меня был коллега, который когда-то синхронизировал вещи "на всякий случай - ведь синхронизировать должно быть лучше, верно?" Это НЕПРАВИЛЬНО и стало причиной множества ошибок взаимоблокировок.
  2. Если ваши объекты могут быть неизменными, сделайте их неизменными. Это не только поможет с потоками, поможет их безопасно использовать в наборах, как ключи для карт и т. Д.
  3. Держите ваши объекты как можно проще. Каждый в идеале должен делать только одну работу. Если вы когда-нибудь обнаружите, что хотите синхронизировать доступ с половиной участников, вам, возможно, следует разделить объект на две части.
  4. Изучите java.util.concurrent и используйте его при любой возможности. Их код будет лучше, быстрее и безопаснее, чем ваш (или мой) в 99% случаев.
  5. Чтение Параллельное программирование на Java , это здорово!
3 голосов
/ 12 марта 2009

В качестве дополнительного замечания: Синхронизация! = Потокобезопасность. Тем не менее, вы не можете одновременно изменять данные, но вы можете читать их одновременно. Поэтому следует помнить о модели памяти Java, где синхронизация означает обеспечение надежности данных, доступных во всех потоках, а не только защиту их одновременной модификации.

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

2 голосов
/ 25 октября 2008

Я нашел аннотации JCIP очень полезными для объявления, какие классы являются поточно-ориентированными. Моя команда комментирует наши классы как @ ThreadSafe , @ NotThreadSafe или @ Immutable . Это гораздо понятнее, чем читать Javadoc, и FindBugs также помогает нам находить нарушения контрактов @Immutable и @ GuardedBy .

1 голос
/ 08 марта 2012

Вот мой личный подход:

  • Сделать объекты и структуру данных неизменными везде, где вы можете. Это хорошая практика в целом, и она автоматически защищена от потоков. Проблема решена.
  • Если вам нужно сделать объект изменчивым, то обычно не пытайтесь сделать его потокобезопасным . Причина этого проста: когда у вас изменяемое состояние, блокировка / управление не могут быть безопасно обработаны одним классом. Даже если вы синхронизируете все методы, это не гарантирует безопасность потоков. И если вы добавляете синхронизацию к объекту, который когда-либо используется только в однопоточном контексте, то вы просто добавили ненужные накладные расходы. Таким образом, вы могли бы также предоставить вызывающей стороне / пользователю возможность реализовать любую необходимую систему блокировки.
  • Если вы предоставляете открытый API более высокого уровня, тогда реализует любую блокировку, необходимую для обеспечения безопасности вашего потока API . Для функциональности более высокого уровня издержки безопасности потоков довольно тривиальны, и ваши пользователи обязательно будут вам благодарны. API со сложной семантикой параллелизма, который должен обходиться пользователям, не является хорошим API!

Этот подход хорошо послужил мне со временем: вам, возможно, придется сделать случайное исключение, но в среднем это очень хорошее место для начала!

1 голос
/ 24 октября 2008

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

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

Остальное не делает, и поэтому сделать его поточно-ориентированным - пустая трата времени.

Например, с помощью Swing GUI Sun просто решила, что ни один из них не будет многопоточным.

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

Sun изначально выпустила поточно-безопасные коллекции (только). проблема в том, что потокобезопасность не может быть сделана не-потокобезопасной (в целях производительности). Так что теперь они выпустили версии без использования потоков с оболочками, чтобы сделать их безопасными. В большинстве случаев обертки не нужны - предположим, что если вы сами не создаете потоки, то ваш класс не обязательно должен быть безопасным для потоков - но ДОКУМЕНТуйте его в javadocs.

0 голосов
/ 03 ноября 2011

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

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

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

0 голосов
/ 28 сентября 2009

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

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

Просто сохраняйте текущее резюме вашего бургера.

...