Мне поручили работать над некоторыми проблемами производительности и случайными сбоями многопоточного Java-сервера. Несмотря на то, что потоки и безопасность потоков не являются для меня новыми темами, я обнаружил, что разработка нового многопоточного приложения, вероятно, вполовину так же сложна, как попытка настроить какой-либо устаревший код. Я просмотрел некоторые известные книги в поисках ответов, но странно то, что пока я читаю об этом и анализирую приведенные примеры, все кажется ясным. Однако, когда я смотрю на код, над которым должен работать, я уже ни в чем не уверен! Должно быть слишком много теоретических знаний и мало реального опыта или что-то в этом роде.
В любом случае, возвращаясь к теме, когда я проводил онлайн-исследования, я наткнулся на эта часть кода. Меня постоянно беспокоит вопрос: Действительно ли безопасно вызывать getInputStream() и getOutputStream() в сокете из двух отдельных потоков без синхронизации? Или я теперь слишком параноидально смотрю на все это в целом? проблема с безопасностью потоков? Угадайте, что происходит, когда пятая книга подряд рассказывает вам, сколько вещей может пойти не так с параллелизмом.
PS. Извините, если вопрос слишком длинный или, может быть, слишком «нубийский», пожалуйста, будьте со мной полегче — это мой первый пост здесь.
Редактировать: Просто для ясности: я знаю, что сокеты работают в полнодуплексном режиме, и безопасно одновременно использовать их потоки ввода и вывода. Мне кажется, это нормально, когда вы получаете эти ссылки в основном потоке, а затем инициализируете ими объекты потока, но также безопасно ли получать эти потоки в двух разных потоках?
@rsp:
Итак, я проверил код Sun, и PlainSocketImpl
действительно синхронизирует эти два метода, как вы и сказали. Socket
, однако, нет. getInputStream()
и getOutputStream()
в значительной степени являются просто оболочками для SocketImpl
, поэтому вероятно проблемы параллелизма не приведут к взрыву всего сервера. Тем не менее, с некоторым неудачным временем, кажется, что что-то может пойти не так (например, когда какой-то другой поток закрывает сокет, когда метод уже проверил наличие условий ошибки).
Как вы указали, с точки зрения структуры кода было бы неплохо предоставить каждому потоку ссылку на поток вместо всего сокета. Я бы, наверное, уже реструктурировал код, над которым я работаю, если бы не тот факт, что каждый поток также использует метод close()
сокета (например, когда сокет получает команду «выключить»). Насколько я могу судить, основная цель этих потоков — поставить сообщения в очередь для отправки или обработки, так что, возможно, это нарушение принципа единой ответственности, и эти потоки не должны иметь возможность закрывать сокет (сравните с Отдельный модемный интерфейс)? Но затем, если я буду слишком долго анализировать код, окажется, что дизайн в целом ошибочен, и все это требует переписывания. Даже если бы руководство было готово заплатить цену, серьезный рефакторинг устаревшего кода, полное отсутствие юнит-тестов и решение сложно отлаживаемых проблем параллелизма, вероятно, принесло бы больше вреда, чем пользы. Не так ли?