2008-10-24 6 views
22

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

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

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

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

EDIT 2: Когда я просматриваю советы прагматичного программиста, я видел совет # 41: Всегда создавайте параллелизм. Это не говорит о том, что весь код должен быть потокобезопасным, но он говорит, что дизайн должен иметь параллелизм в виду.

+0

Что-то, что поможет вам в анализе, не думать о параллелизме как о преждевременной оптимизации, а скорее о производительности, связанной с BigO. Преждевременные наблюдения оптимизации-зла относятся к людям, которые разворачивают петли или критические условные конструкции, если условные конструкции в C/C++, которые невозможно расшифровать и поддерживать, и могут не дать вам какого-либо выигрыша по производительности после того, как компилятор завершил работу с кода (или в java runtime JIT). – reccles 2009-11-10 22:53:52

ответ

19

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

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

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

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

0

Если вы хотите следить за тем, что сделал Sun в Java API, вы можете взглянуть на классы коллекции. Многие общие классы коллекций не являются потокобезопасными, но имеют поточно-безопасные копии. По словам Джона Скита (см. Комментарии), многие из классов Java были изначально потокобезопасными, но они не приносили пользу разработчикам, поэтому некоторые классы теперь имеют две версии: один из которых является потокобезопасным, а другой - небезопасным.

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

+0

На самом деле подход Sun был наоборот: исходные классы коллекций (и StringBuffer) были потокобезопасными, тогда они поняли, что это никому не помогает, поэтому, когда они переработали классы коллекций, они сделали их не потоковыми , – 2008-10-24 16:54:14

+0

Действительно? Я исправлю свою публикацию, чтобы убедиться, что история правильная. Но я знаю, что Солнце делало это по соображениям производительности, и это было моим моментом. Благодарю. – 2008-10-24 16:56:01

0

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

однотридовых из них намного проще работать.

Разделительная многопоточную логики помогает сделать синхронизации правильно.

26

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

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

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

+0

У вас есть пример, где использование может нарушить безопасность потоков класса? – Mnementh 2008-10-24 17:29:40

+7

Очень легко - возьмите коллекцию, где каждая отдельная операция само по себе является потокобезопасной. Теперь перейдем к нему - взломать, потоковая безопасность ушла. Вы не хотите, чтобы каждая отдельная операция вынимала блокировку - вам нужно блокировать все время, когда вы итерации. Сам сборщик не может этого сделать для вас. – 2008-10-24 18:43:08

6

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

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

1

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

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

Остальное не делает и поэтому делает его поточно-безопасным будет пустой тратой.

Например, с графическим интерфейсом Swing Sun просто решил, что ни одно из них не будет многопоточным.

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

Солнце первоначально вышло с потоковыми коллекциями (только). проблема заключается в том, что потокобезопасность не может быть выполнена без потоковой (для служебных целей). Итак, теперь они вышли с не-потоковыми версиями с обертками, чтобы сделать их потокобезопасными. В большинстве случаев обертки не нужны - предположим, что, если вы сами не создаете потоки, ваш класс не должен быть потокобезопасным, но DOCUMENT это в javadocs.

0

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

4

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

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

  1. Самое главное: Подумайте, прежде чем вы синхронизируете. У меня был один коллега, который раньше синхронизировал материал «на всякий случай», ведь синхронизация должна быть лучше, не так ли? » Это НЕПРАВИЛЬНО, и это стало причиной множества ошибок в тупике.
  2. Если ваши объекты могут быть неизменными, сделайте их неизменными. Это поможет не только потоковому использованию, но и поможет их безопасно использовать в наборах, в качестве ключей для карт и т. Д.
  3. Держите свои объекты как можно проще. Каждый из них должен идеально выполнять только одну работу. Если вы когда-либо находите, вы можете синхронизировать доступ к половине членов, то вам, возможно, придется разделить объект на два.
  4. Изучите java.util.concurrent и используйте его, когда это возможно. Их код будет лучше, быстрее и безопаснее, чем ваш (или мой) в 99% случаев.
  5. Прочитано Concurrent Programming in Java, это здорово!
2

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

3

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

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

0

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

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

Просто держите ваш гамбургер flippin resume 'current.

0

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

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

Однако многопоточность является фундаментальным (архитектурным) выбором в программе. Это not really something to add as an after thought. Поэтому с самого начала вы должны знать, какие классы должны быть потокобезопасными.

1

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

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

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