Лекции 2025. Java. Белая / Ответы на билеты. Java
.pdf
2.Он пытается взять "ключ" (захватить монитор).
3.Если "ключ" свободен, поток берет его, входит в "комнату", выполняет работу. По завершении он кладет "ключ" на место (освобождает монитор).
4.Если "ключ" занят другим потоком, текущий поток ждет у двери (блокируется), пока "ключ" не освободится.
5.Если поток внутри "комнаты" решает, что ему нужно подождать какого-то условия
(вызывает
), он временно кладет "ключ" на место (освобождает монитор) и уходит "спать" в специальную "комнату ожидания".
6.Когда другой поток, владеющий "ключом", изменяет условие и вызывает
или
, он "будит" одного или всех "спящих" потоков. Пробужденные потоки снова пытаются взять "ключ", чтобы проверить условие.
Использование:
Мониторы являются основой для ключевого слова
в Java, которое является основным способом обеспечения взаимного исключения и координации потоков при доступе к разделяемым данным.
Хотя концепция монитора не всегда явно видна программисту (она скрыта за
), понимание ее работы критически важно для написания корректных многопоточных программ на Java. Более сложные механизмы синхронизации в
(например,
,
) предоставляют более гибкий и мощный контроль над блокировками и условиями, но они также часто основаны на концепциях, схожих с мониторами.
63. Дайте определение понятию «синхронизация».
Синхронизация в контексте многопоточного программирования — это механизм
координации доступа нескольких потоков к общим (разделяемым) ресурсам или данным с целью предотвращения их одновременного изменения и обеспечения предсказуемого и корректного поведения программы.
Основные цели и аспекты синхронизации:
1. Взаимное исключение (Mutual Exclusion):
Гарантия того, что только один поток может одновременно выполнять определенный участок кода (критическую секцию) или получать доступ к определенному разделяемому ресурсу.
Это предотвращает состояния гонки (race conditions), когда результат операции зависит от непредсказуемого порядка выполнения инструкций разными потоками, что может привести к повреждению данных или некорректному состоянию.
2. Видимость изменений (Visibility):
Обеспечение того, что изменения, сделанные одним потоком в разделяемых данных, становятся видимыми для других потоков.
Без синхронизации изменения, сделанные одним потоком, могут быть кешированы в его локальной памяти (например, кеше процессора) и не сразу отражены в основной памяти, делая их невидимыми для других потоков. Механизмы синхронизации обычно устанавливают отношения "happens-before", которые гарантируют видимость.
3. Упорядочивание операций (Ordering):
В некоторых случаях синхронизация помогает контролировать порядок выполнения операций между потоками, чтобы избежать нежелательных перестановок инструкций компилятором или процессором, которые могут нарушить логику программы в многопоточной среде.
4. Координация потоков:
Позволяет потокам взаимодействовать и координировать свои действия. Например, один поток может ждать, пока другой поток не завершит определенную работу или не изменит состояние разделяемого ресурса до определенного значения (например, с помощью
,
,
).
Зачем нужна синхронизация?
В многопоточных приложениях несколько потоков могут выполняться параллельно или псевдопараллельно. Если эти потоки обращаются к общим данным без какой-либо координации, могут возникнуть серьезные проблемы:
Состояния гонки (Race Conditions): Несколько потоков пытаются одновременно изменить общее состояние, и конечный результат зависит от того, какой поток "выиграет гонку". Например, два потока инкрементируют общий счетчик (
не является атомарной операцией).
Проблемы видимости (Visibility Problems): Один поток изменяет данные, но другой поток не видит этих изменений и работает со старыми (кешированными) значениями.
Взаимные блокировки (Deadlocks): Два или более потока бесконечно ожидают друг друга, так как каждый из них заблокировал ресурс, необходимый другому потоку.
"Живые" блокировки (Livelocks): Потоки активно выполняют действия, но не могут достичь прогресса, постоянно реагируя на действия друг друга. Голодание потоков (Starvation): Один или несколько потоков не могут получить доступ к ресурсу в течение длительного времени, потому что другие потоки постоянно его занимают.
Механизмы синхронизации в Java:
Ключевое слово
: Использует внутренние мониторы объектов для обеспечения взаимного исключения. Может применяться к методам или блокам кода.
Методы
,
,
класса
: Используются для координации потоков на основе условий, всегда в связке с
. Ключевое слово
: Гарантирует видимость изменений переменной между потоками и предотвращает некоторые виды переупорядочивания инструкций, но не обеспечивает атомарность сложных операций.
Классы из пакета
: Предоставляют более продвинутые и гибкие инструменты синхронизации:
Блокировки (Locks):
,
(для более тонкого контроля над блокировками).
Условные переменные (Conditions): Связаны с
, аналог
для
.
Атомарные переменные (Atomic Variables):
,
,
и др. (для выполнения атомарных операций без явных блокировок).
Синхронизаторы:
,
,
,
,
(для координации групп потоков).
Потокобезопасные коллекции:
,
и
др.
В итоге, синхронизация — это набор техник и инструментов, которые позволяют
разработчикам писать корректные, надежные и предсказуемые многопоточные
программы, управляя доступом к разделяемым ресурсам и координируя взаимодействие между потоками.
64. Какие существуют способы синхронизации в Java?
Java предоставляет разнообразные способы и механизмы для синхронизации потоков. Их можно условно разделить на несколько категорий:
1. Базовые механизмы на уровне языка и JVM:
Ключевое слово
:
Назначение: Обеспечивает взаимное исключение (mutex) при доступе к блоку кода или методу. Использует внутренний монитор объекта.
Применение:
Синхронизированные методы экземпляра: 
(блокировка на
).
Синхронизированные статические методы: 
(блокировка на объекте
данного класса).
Синхронизированные блоки кода: 
(блокировка на указанном объекте
).
Гарантии: Обеспечивает, что только один поток может выполнять синхронизированный код на данном мониторе в любой момент времени. Также устанавливает отношения "happens-before", гарантируя видимость изменений.
Методы
,
,
класса
:
Назначение: Используются для координации потоков, позволяя им ожидать выполнения определенных условий.
Использование: Должны вызываться только внутри
блока
или метода на объекте, чей монитор используется для синхронизации.
: Освобождает монитор и переводит поток в состояние ожидания.
: Пробуждает один ожидающий поток.
: Пробуждает все ожидающие потоки.
Гарантии: Позволяют потокам эффективно ожидать событий, не потребляя процессорное время впустую.
Ключевое слово
:
Назначение: Гарантирует, что чтение и запись
переменной происходят непосредственно из/в основную память, минуя кеши потоков. Обеспечивает видимость изменений переменной между потоками. Также накладывает некоторые ограничения на переупорядочивание инструкций компилятором и процессором.
Ограничения:
не гарантирует атомарность для сложных операций (например, инкремент
не атомарен). Подходит для простых флагов или переменных, где запись не зависит от предыдущего значения.
2. Расширенные механизмы из пакета
(JUC):
Блокировки (Locks) - интерфейс
:
Более гибкая и мощная альтернатива
.
Основные реализации:
: Повторно входимая блокировка, аналогичная
, но с дополнительными возможностями (например, попытка захвата с таймаутом
, возможность прерывания ожидания блокировки, получение информации о владельце блокировки и ожидающих потоках).
: Позволяет одновременно нескольким потокам читать разделяемый ресурс, но только одному потоку — писать. Состоит из
и
. Улучшает производительность для сценариев с частым чтением и редкой записью.
Использование: Явный захват (
) и освобождение (
, обычно в
блоке).
Условные переменные (Conditions) - интерфейс
:
Связаны с объектами
(получаются через
).
Предоставляют более гибкий механизм ожидания/уведомления, чем
для
. Позволяют создавать несколько наборов условий для одной блокировки.
Методы:
,
,
.
Атомарные переменные (Atomic Variables) - пакет
:
Классы, такие как
,
,
,
.
Предоставляют атомарные операции (например, инкремент, декремент, сравнение и установка
) без использования явных блокировок.
Работают на основе низкоуровневых инструкций процессора (CAS - Compare- And-Swap). Очень эффективны для управления простыми счетчиками или флагами в многопоточной среде.
Синхронизаторы (Synchronizers):
Классы, помогающие координировать взаимодействие между несколькими потоками:
: Ограничивает количество потоков, которые могут одновременно получить доступ к определенному ресурсу или участку
кода (управляет набором "разрешений").
: Позволяет одному или нескольким потокам ожидать, пока не завершится определенное количество операций, выполняемых другими потоками. Работает как "одноразовый барьер".
: Позволяет группе потоков ожидать друг друга в определенной точке ("барьере") перед тем, как продолжить выполнение. Барьер можно использовать многократно. Может выполнять действие по достижении барьера.
: Более гибкий и мощный синхронизатор, чем
и
. Позволяет динамически изменять количество участвующих потоков и организовывать многофазную синхронизацию.
: Позволяет двум потокам обменяться данными в точке рандеву.
Потокобезопасные коллекции (Concurrent Collections) - пакет
:
Коллекции, разработанные для безопасного использования в многопоточной среде без необходимости внешней синхронизации со стороны клиента (или с очень специфичными правилами).
Примеры:
: Высокопроизводительная потокобезопасная реализация
.
/
: Потокобезопасные списки/ множества, где операции модификации создают копию внутреннего массива. Эффективны для сценариев, где чтений гораздо больше, чем записей.
Блокирующие очереди (
интерфейс и его реализации, например,
,
,
,
): Используются для организации взаимодействия потоков по типу "производительпотребитель".
3. Пулы потоков (Executor Framework) - пакет
:
,
,
,
,
.
Хотя сами по себе не являются примитивами синхронизации, они играют ключевую роль в управлении выполнением асинхронных задач и часто используются совместно с другими механизмами синхронизации для построения сложных многопоточных приложений. Они управляют жизненным циклом рабочих потоков и очередями задач.
Выбор конкретного способа синхронизации зависит от задачи:
Для простых случаев взаимного исключения |
часто достаточно. |
|
|
— для обеспечения видимости простых флагов. |
|
и |
— для более сложного контроля над блокировками и |
|
условиями. |
|
|
Атомарные переменные — для высокопроизводительных атомарных операций над одиночными переменными.
Синхронизаторы — для координации групп потоков.
Потокобезопасные коллекции — для безопасной работы с общими структурами данных.
65. В каких состояниях может находиться поток?
В Java поток (
) может находиться в одном из нескольких состояний на протяжении своего жизненного цикла. Эти состояния определены в перечислении
.
Вот основные состояния потока:
1.
(Новый):
Поток был создан (сконструирован объект
), но метод
для него еще не был вызван.
В этом состоянии поток еще не является "живым" потоком операционной системы и не может выполняться.
2.
(Готовый к выполнению / Выполняемый):
Это состояние означает, что поток готов к выполнению и ожидает, когда планировщик потоков JVM выделит ему процессорное время, или он уже выполняется в данный момент.
Поток переходит в это состояние после вызова метода
.
Он также может вернуться в это состояние из других состояний (например, из
,
,
) после того, как условие блокировки будет снято или таймаут истечет.
Важно понимать, что
объединяет два подсостояния с точки зрения ОС: "готовый" (ready) и "выполняемый" (running). JVM не делает между ними различия на уровне
.
3.
(Заблокированный):
Поток находится в этом состоянии, когда он ожидает освобождения монитора (внутренней блокировки), чтобы войти в
блок/метод, или после вызова
и последующего
/
, когда он снова пытается захватить монитор.
Поток блокируется, если пытается войти в
секцию, монитор которой уже занят другим потоком.
Как только монитор освобождается, один из заблокированных потоков (если их несколько) переходит в состояние
и пытается захватить монитор.
4. |
(Ожидающий): |
|
|
|
|
|
Поток находится в состоянии неограниченного ожидания, пока другой поток не |
||||
|
выполнит определенное действие для его "пробуждения". |
|
|||
|
Поток переходит в это состояние в следующих случаях: |
|
|||
|
Вызов |
|
(без таймаута) на объекте-мониторе. Поток |
||
|
освобождает монитор и ждет, пока другой поток не вызовет |
или |
|||
|
на том же объекте. |
|
|
||
|
Вызов |
|
(без таймаута) на другом потоке. Текущий поток |
||
|
ждет, пока указанный другой поток не завершится. |
|
|||
|
Вызов |
|
. |
|
|
|
Поток, находящийся в |
|
, не потребляет процессорное время. |
|
|
5. |
(Ожидающий в течение определенного времени): |
|
|||
|
Поток находится в состоянии ожидания в течение указанного периода времени. |
||||
|
Поток переходит в это состояние в следующих случаях: |
|
|||
|
Вызов |
|
|
. |
|
|
Вызов |
|
|
на объекте-мониторе. |
|
|
Вызов |
|
|
на другом потоке. |
|
|
Вызов |
|
|
или |
. |
|
Методы блокировок из |
|
с таймаутом |
||
|
(например, |
|
|
), если они |
|
|
блокируют поток. |
|
|
|
|
|
Методы условных переменных ( |
|
|
||
|
). |
|
|
|
|
|
Поток вернется в состояние |
либо по истечении указанного времени, |
|||
|
либо если он будет "пробужден" раньше (например, через |
для |
|||
|
, или если |
|
завершится раньше). |
|
|
|
Поток в |
также не потребляет процессорное время (пока |
|||
|
ожидает). |
|
|
|
|
6.
(Завершенный):
Поток завершил свое выполнение (метод
нормально завершился или был прерван и завершился из-за необработанного исключения).
Поток в этом состоянии больше не может быть запущен снова.
Диаграмма переходов между состояниями (упрощенная):
NE
star
RUNN
Выброшено Завершение run() необработанное
исключение
Попытка входа в
TERMINATE synchronized (монито занят)
Планировщик выбирает
Примечание: Переходы после
из
сначала ведут в
, так как поток должен снова получить монитор, прежде чем стать
.
Получить текущее состояние потока можно с помощью метода
, который возвращает значение из перечисления
. Понимание состояний потока важно для отладки и анализа многопоточных приложений, особенно при поиске взаимных блокировок или проблем с производительностью.
66. Как работают методы wait() и notify()/notifyAll()?
Методы
,
и
являются фундаментальными механизмами для координации потоков в Java. Они определены в классе
, что означает, что они доступны для любого объекта, так как каждый объект в Java имеет связанный с ним монитор.
Ключевые моменты:
Использование с монитором: Эти методы всегда используются в связке с монитором объекта (внутренней блокировкой). Они должны вызываться только из
блока или
метода, когда текущий поток владеет монитором того объекта, на котором вызываются эти методы. Если это условие не выполнено, будет выброшено исключение
. Назначение: Позволяют потокам эффективно ожидать выполнения определенных условий, не потребляя процессорное время впустую (в отличие от активного ожидания в цикле - "спинлока").
(и его перегруженные версии
и
):
1. Действие:
Когда поток вызывает
, он освобождает монитор объекта
.
Это очень важно, так как это позволяет другим потокам захватить монитор и, возможно, изменить условие, которого ожидает первый поток.
Затем поток переходит в состояние ожидания (
или
если использована версия с таймаутом).
Поток остается в состоянии ожидания до тех пор, пока не произойдет одно из следующих событий:
Другой поток вызовет
или
на том же объекте
.
Истечет указанный таймаут (для
).
Поток будет прерван (
). В этом случае
выбросит
.
2. После пробуждения:
Когда поток пробуждается (например, после
), он не сразу продолжает выполнение.
Он сначала должен снова попытаться захватить монитор объекта
. Он переходит в состояние
и конкурирует за монитор с другими потоками, которые также могут пытаться его захватить.
Только после того, как поток успешно снова захватит монитор, он сможет продолжить выполнение инструкций после вызова
.
3. Проверка условия в цикле (
):
Очень важно вызывать
внутри цикла
, который проверяет условие ожидания. Это необходимо из-за так называемых ложных пробуждений (spurious wakeups), когда поток может быть пробужден без явного вызова
/
или прерывания (хотя это редкое явление, спецификация это допускает). Также это необходимо, потому что после
условие могло снова измениться до того, как пробужденный поток захватил монитор.
