Это своего рода опрос об общих проблемах параллелизма в Java. Примером может служить классический тупик или состояние гонки или ошибки EDT в Swing. Меня интересует как масса возможных проблем, так и вопросы, которые наиболее распространены. Поэтому, пожалуйста, оставьте один конкретный ответ об ошибке параллелизма Java на комментарий и проголосуйте, если вы встретите такой.
java
multithreading
concurrency
Алекс Миллер
источник
источник
Ответы:
Самая распространенная проблема параллелизма, с которой я столкнулся, это отсутствие понимания того, что поле, написанное одним потоком, не гарантированно будет видно другому потоку. Распространенное применение этого:
До тех пор пока остановки не летучий или
setStop
иrun
не синхронизированы это не гарантирует работу. Эта ошибка особенно дьявольская, поскольку на 99,999% она не будет иметь значения на практике, поскольку читательская ветка в конечном итоге увидит изменения - но мы не знаем, как скоро он это увидел.источник
Моя самая болезненная проблема параллелизма когда-либо возникала, когда две разные библиотеки с открытым исходным кодом делали что-то вроде этого:
На первый взгляд это выглядит довольно тривиальным примером синхронизации. Тем не мение; поскольку строки являются интернированными в Java, буквальная строка
"LOCK"
оказывается одним и тем же экземпляромjava.lang.String
(даже если они объявляются совершенно независимо друг от друга.) Результат явно плохой.источник
Одна классическая проблема - это изменение объекта, с которым вы синхронизируете, при синхронизации:
Затем другие параллельные потоки синхронизируются с другим объектом, и этот блок не обеспечивает ожидаемого взаимного исключения.
источник
Распространенной проблемой является использование классов, таких как Calendar и SimpleDateFormat, из нескольких потоков (часто путем кэширования их в статической переменной) без синхронизации. Эти классы не являются поточно-ориентированными, поэтому многопоточный доступ в конечном итоге вызовет странные проблемы с несовместимым состоянием.
источник
Неправильная синхронизация объектов, возвращаемых
Collections.synchronizedXXX()
, особенно во время итерации или нескольких операций:Это неправильно . Несмотря на то
synchronized
, что выполняется одна операция , состояние отображения между вызовамиcontains
иput
может быть изменено другим потоком. Так должно быть:Или с
ConcurrentMap
реализацией:источник
Двойная проверка блокировки. В общем и целом.
Парадигма, с которой я начал изучать проблемы, связанные с работой в BEA, заключается в том, что люди будут проверять синглтон следующим образом:
Это никогда не работает, потому что другой поток мог попасть в синхронизированный блок, а s_instance больше не равен нулю. Таким образом, естественное изменение состоит в том, чтобы сделать это:
Это тоже не работает, потому что Java Memory Model не поддерживает это. Вам нужно объявить s_instance как volatile, чтобы он работал, и даже тогда он работает только на Java 5.
Люди, которые не знакомы с тонкостями модели памяти Java, все время путаются .
источник
Хотя, вероятно, это не совсем то, о чем вы просите, наиболее частая проблема, связанная с параллелизмом, с которой я столкнулся (вероятно, потому, что она возникает в обычном однопоточном коде), это
java.util.ConcurrentModificationException
вызвано такими вещами, как:
источник
Может быть легко думать, что синхронизированные коллекции предоставляют вам большую защиту, чем они на самом деле, и забывают удерживать блокировку между вызовами. Я видел эту ошибку несколько раз:
Например, во второй строке выше,
toArray()
иsize()
методы являются поточно в своем собственном праве, ноsize()
оцениваются отдельно отtoArray()
, и замок в списке не проводятся между этими двумя вызовами.Если вы запустите этот код с другим потоком, одновременно удаляя элементы из списка, рано или поздно вы получите новый
String[]
возвращаемый результат, который больше, чем требуется для хранения всех элементов в списке, и имеет нулевые значения в хвосте. Легко подумать, что поскольку два вызова метода для List происходят в одной строке кода, это как-то атомарная операция, но это не так.источник
Самая распространенная ошибка, с которой мы сталкиваемся, это то, где я работаю, программисты выполняют длинные операции, такие как серверные вызовы, над EDT, блокируя графический интерфейс на несколько секунд и делая приложение не отвечающим на запросы.
источник
Забыть wait () (или Condition.await ()) в цикле, проверяя, что условие ожидания действительно истинно. Без этого вы можете столкнуться с ошибками в результате ложных пробуждений wait (). Каноническое использование должно быть:
источник
Другая распространенная ошибка - плохая обработка исключений. Когда фоновый поток генерирует исключение, если вы не обработаете его должным образом, вы можете вообще не увидеть трассировку стека. Или, возможно, ваша фоновая задача перестает выполняться и больше не запускается, потому что вы не обработали исключение.
источник
Пока я не взял урок с Брайаном Гетцем, я не понимал, что несинхронизированное
getter
частное поле, мутировавшее через синхронизированноеsetter
, никогда не сможет вернуть обновленное значение. Только когда переменная защищена синхронизированным блоком в обеих операциях чтения и записи , вы получите гарантию на последнее значение переменной.источник
Думая, что вы пишете однопоточный код, но с использованием изменяемой статики (включая синглтоны). Очевидно, они будут разделены между потоками. Это случается на удивление часто.
источник
Произвольные вызовы методов не должны выполняться из синхронизированных блоков.
Дейв Рэй коснулся этого в своем первом ответе, и на самом деле я также столкнулся с тупиком, также связанным с вызовом методов для слушателей из синхронизированного метода. Я думаю, что более общий урок состоит в том, что вызовы методов не должны быть сделаны "в дикую природу" из синхронизированного блока - вы не представляете, будет ли вызов длительным, приведет к тупику или как-то еще.
В этом случае, и обычно в целом, решение заключалось в том, чтобы уменьшить область синхронизированного блока, чтобы просто защитить критическую частную часть кода.
Кроме того, поскольку теперь мы обращались к коллекции слушателей вне синхронизированного блока, мы изменили ее на коллекцию копирования при записи. Или мы могли бы просто сделать защитную копию Коллекции. Дело в том, что обычно есть альтернативы для безопасного доступа к Коллекции неизвестных объектов.
источник
Самая последняя ошибка, связанная с параллелизмом, с которой я столкнулся, была объектом, который в своем конструкторе создал ExecutorService, но когда на объект больше не ссылались, он никогда не завершал работу ExecutorService. Таким образом, за несколько недель просочились тысячи потоков, что в итоге привело к сбою системы. (Технически, он не падал, но он перестал работать должным образом, продолжая работать.)
Технически, я предполагаю, что это не проблема параллелизма, но это проблема, связанная с использованием библиотек java.util.concurrency.
источник
Несбалансированная синхронизация, особенно с картами, кажется довольно распространенной проблемой. Многие люди считают, что синхронизация по путам на карту (не ConcurrentMap, но, скажем, HashMap) и отсутствие синхронизации при получении достаточны. Это, однако, может привести к бесконечному циклу во время повторного хеширования.
Та же проблема (частичная синхронизация) может возникнуть в любом месте, где у вас есть общее состояние чтения и записи.
источник
Я столкнулся с проблемой параллелизма с сервлетами, когда есть изменяемые поля, которые будут устанавливаться каждым запросом. Но существует только один экземпляр сервлета для всех запросов, поэтому он отлично работал в однопользовательской среде, но когда несколько пользователей запросили сервлет, возникли непредсказуемые результаты.
источник
Не совсем баг, но наихудшим грехом является предоставление библиотеки, которую вы собираетесь использовать другим людям, но не указание, какие классы / методы являются поточно-ориентированными, а какие должны вызываться только из одного потока и т. Д.
Все больше людей должны использовать аннотации параллелизма (например, @ThreadSafe, @GuardedBy и т. Д.), Описанные в книге Гетца.
источник
Моя самая большая проблема всегда заключалась в тупиках, особенно вызванных слушателями, которые запускаются с удерживаемой блокировкой. В этих случаях действительно легко получить инвертированную блокировку между двумя потоками. В моем случае, между симуляцией, запущенной в одном потоке, и визуализацией симуляции, запущенной в потоке пользовательского интерфейса.
РЕДАКТИРОВАТЬ: перенес второй части в отдельный ответ.
источник
Запуск потока внутри конструктора класса проблематичен. Если класс расширен, поток может быть запущен до выполнения конструктора подкласса .
источник
Изменчивые классы в общих структурах данных
Когда это происходит, код становится намного сложнее, чем этот упрощенный пример. Воспроизвести, найти и исправить ошибку сложно. Возможно, этого можно было бы избежать, если бы мы могли пометить определенные классы как неизменяемые, а определенные структуры данных как содержащие только неизменяемые объекты.
источник
Синхронизация строкового литерала или константы, определенной строковым литералом, (потенциально) является проблемой, поскольку строковый литерал интернирован и будет использоваться любым другим пользователем в JVM с использованием того же строкового литерала. Я знаю, что эта проблема возникла в серверах приложений и других «контейнерных» сценариях.
Пример:
В этом случае любой, кто использует строку «foo» для блокировки, использует одну и ту же блокировку.
источник
Я полагаю, что в будущем главной проблемой Java будет (отсутствие) гарантий видимости для конструкторов. Например, если вы создаете следующий класс
а затем просто прочитайте свойство MyClass a из другого потока, MyClass.a может иметь значение 0 или 1, в зависимости от реализации и настроения JavaVM. Сегодня шансы «а» быть 1 очень высоки. Но на будущих машинах NUMA это может быть иначе. Многие люди не знают об этом и считают, что им не нужно заботиться о многопоточности на этапе инициализации.
источник
Самая глупая ошибка, которую я часто допускаю, - это забыть синхронизацию перед вызовом notify () или wait () для объекта.
источник
Использование локального "new Object ()" в качестве мьютекса.
Это бесполезно.
источник
Другая распространенная проблема «параллелизма» - использование синхронизированного кода, когда он вообще не нужен. Например, я все еще вижу программистов, использующих
StringBuffer
или дажеjava.util.Vector
(как локальные переменные метода).источник
Несколько объектов, которые защищены от блокировки, но обычно доступны последовательно. Мы столкнулись с парой случаев, когда блокировки были получены с помощью разного кода в разных порядках, что приводило к тупику.
источник
Не осознавая, что
this
внутренний класс неthis
является внешним классом. Обычно в анонимном внутреннем классе, который реализуетRunnable
. Основная проблема заключается в том, что, поскольку синхронизация является частью всех функций,Object
фактически отсутствует статическая проверка типов. Я видел это, по крайней мере, два раза в Usenet, и это также появляется в Brian Goetz'z Java Concurrency на практике.Закрытия BGGA от этого не страдают, так как
this
для замыканий нет (this
ссылается на внешний класс). Если вы используете не-this
объекты в качестве блокировок, то обойдется эта проблема и другие.источник
Использование глобального объекта, такого как статическая переменная для блокировки.
Это приводит к очень плохой производительности из-за разногласий.
источник
Honesly? До появления
java.util.concurrent
самой распространенной проблемы, с которой я обычно сталкивался, было то, что я называю «перебрасыванием потоков»: приложения, которые используют потоки для параллелизма, но порождают слишком много из них и в итоге перебивают.источник