Опишите гарантию видимости, которую Exchanger обеспечивает после успешного двустороннего обмена потоков.
После успешного вызова Exchanger.exchange() действия каждого потока, выполненные до обмена, становятся видимыми другому потоку после его успешного возврата из exchange(). Обмен одновременно передаёт ссылку на объект и служит точкой синхронизации между двумя потоками.
В многопоточных программах часто требуется не просто передать отдельное значение, а безопасно обменять целые наборы данных между двумя постоянно работающими потоками. Например, один поток заполняет буфер, а другой обрабатывает его, после чего буферы можно поменять местами.
Без специального примитива такую схему пришлось бы строить на общей переменной, состояниях готовности и ручной синхронизации. Exchanger предоставляет готовую точку rendezvous: каждый участник ждёт партнёра и получает его объект.
Если поток передаёт ссылку через обычное общее поле без синхронизации, другой поток может не получить корректную публикацию состояния объекта. Простая запись ссылки не заменяет отношения happens-before и не делает обмен безопасным.
Кроме того, при ручной реализации обмена легко получить гонку, потерю уведомления, взаимное ожидание или повторное использование объекта до завершения его обработки. Ошибки особенно вероятны, когда потоки работают циклически и обмениваются буферами много раз.
Exchanger<V> синхронизирует ровно пару участников одного обмена. Каждый поток передаёт объект типа V и блокируется, пока другой поток не передаст свой объект. После сопоставления оба вызова возвращают объект, переданный партнёром.
Гарантия памяти формулируется так: действия до вызова exchange() одного потока происходят до действий после успешного возврата из exchange() другого потока. Поэтому обычные поля переданного объекта можно читать без дополнительного volatile, если объект опубликован через успешный обмен и после передачи не изменяется конкурентно.
В примере поток a после успешного обмена получает буфер потока b и может надёжно прочитать значение 20. Это не означает, что Exchanger защищает последующие совместные изменения: если оба потока продолжат одновременно менять один и тот же объект, понадобится отдельная синхронизация или другая архитектура.
Если обмен не состоялся из-за InterruptedException или истечения времени у варианта с тайм-аутом, гарантия успешной передачи для этого вызова отсутствует. Прерванный поток должен восстановить флаг прерывания или корректно завершить операцию, как показано в примере.
В системе поток-производитель заполнял большие массивы, а поток-потребитель обрабатывал их. Рассматривались три варианта:
synchronized и флагами: позволяет реализовать схему, но создаёт сложную логику состояний и риск ошибок при повторных циклах;Выбрали Exchanger, потому что потокам требовался именно попарный обмен двумя переиспользуемыми буферами, а не очередь независимых задач. Обмен обеспечил публикацию заполненного буфера и одновременно создал обратное давление: производитель не мог бесконечно создавать новые буферы, пока потребитель не вернул следующий.
Exchanger все последующие изменения переданного объекта потокобезопасными?Ответ: Нет. Гарантия распространяется на действия до успешного обмена и чтения после него. Если после обмена оба потока одновременно изменяют один объект, возникает обычная гонка данных; для таких изменений нужны блокировки, атомарные операции или строгая передача владения объектом.
exchange()?Ответ: Второй участник будет ждать в exchange(), если не используется тайм-аут и поток не прервут. Это свойство rendezvous-механизма: обмен возможен только при наличии пары. На практике применяют exchange(value, timeout, unit), обработку прерывания и контроль жизненного цикла потоков, чтобы отказ одного участника не оставлял другой поток ждать бесконечно.
Exchanger для нескольких пар потоков одновременно?Ответ: Да, вызовы могут выполняться конкурентно, но каждый успешный обмен происходит между двумя потоками, которые встретились на одном экземпляре в совместимый момент времени. Нельзя рассчитывать на заранее определённого партнёра, если одновременно присутствуют несколько участников. Если важны конкретные пары, обычно создают отдельный Exchanger для каждой пары или выбирают другой примитив синхронизации.