ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 01.12.2023

Просмотров: 428

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Удобно представлять mutex как id захватившего его объекта. Если id равен 0 – ресурс свободен. Если не 0 – ресурс занят. Можно встать в очередь и ждать его освобождения.
В Java монитор реализован с помощью ключевого слова synchronized.
Что такое синхронизация? Какие способы синхронизации существуют в
Java?
Синхронизация – это процесс, который позволяет выполнять потоки параллельно.
В Java все объекты имеют блокировку, благодаря которой только один поток одновременно может получить доступ к критическому коду в объекте. Такая синхронизация помогает предотвратить повреждение состояния объекта.
Способы синхронизации в Java:
1. Системная синхронизация с использованием wait()/notify().
Поток, который ждет выполнения каких-либо условий, вызывает у этого объекта метод wait(),
предварительно захватив его монитор. На этом его работа приостанавливается. Другой поток может вызвать на этом же самом объекте метод notify(), предварительно захватив монитор объекта, в результате чего ждущий на объекте поток «просыпается» и продолжает свое выполнение. В обоих случаях монитор надо захватывать в явном виде через synchronized- блок, потому как методы wait()/notify() не синхронизированы!
2. Системная синхронизация с использованием join().
Метод join(), вызванный у экземпляра класса Thread, позволяет текущему потоку остановиться до того момента, как поток, связанный с этим экземпляром, закончит работу.
3. Использование классов из пакета java.util.concurrent.Locks – механизмы синхронизации потоков, альтернативы базовым synchronized, wait, notify, notifyAll:
1   ...   11   12   13   14   15   16   17   18   ...   25

Lock,
Condition, ReadWriteLock.
Как работают методы wait(), notify() и notifyAll()?

wait(): освобождает монитор и переводит вызывающий поток в состояние
ожидания до тех пор, пока другой поток не вызовет метод notify()/notifyAll();

notify(): продолжает работу потока, у которого ранее был вызван метод wait();

notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait().
Когда вызван метод wait(), поток освобождает блокировку на объекте и переходит из состояния «pаботающий» (running) в состояние «ожидание» (waiting). Метод notify() подает
сигнал одному из потоков, ожидающих на объекте, чтобы перейти в состояние
«работоспособный» (runnable). При этом невозможно определить, какой из ожидающих потоков должен стать работоспособным. Метод notifyAll() заставляет все ожидающие потоки для объекта вернуться в состояние «работоспособный» (runnable). Если ни один поток не
находится в ожидании на методе wait(), то при вызове notify() или notifyAll() ничего не
происходит.
wait(), notify() и notifyAll() должны вызываться только из синхронизированного кода.

В каких состояниях может находиться поток?
New – объект класса Thread создан, но еще не запущен. Он еще не является потоком выполнения и естественно не выполняется.
Runnable – поток готов к выполнению, но планировщик еще не выбрал его.
Running – поток выполняется.
Waiting/blocked/sleeping – поток блокирован или ждет окончания работы другого потока.
Dead – поток завершен. Будет выброшено исключение при попытке вызвать метод start() для потока dead.
Что такое семафор? Как он реализован в Java?
Semaphore – это новый тип синхронизатора: семафор со счетчиком, реализующий
шаблон синхронизации «Семафор». Доступ управляется с помощью счетчика:
изначальное значение счетчика задается в конструкторе при создании синхронизатора, когда поток заходит в заданный блок кода, то значение счетчика уменьшается на единицу, когда поток его покидает, то увеличивается. Если значение счетчика равно нулю, то текущий поток блокируется, пока кто-нибудь не выйдет из защищаемого блока. Semaphore используется для защиты дорогих ресурсов, которые доступны в ограниченном количестве, например,
подключение к базе данных в пуле.
Что означает ключевое слово volatile? Почему операции над volatile
переменными не атомарны?
Переменная volatile является атомарной для чтения, но операции над переменной НЕ
являются атомарными. Поля, для которых неприемлемо увидеть «несвежее» (stale)
значение в результате кеширования или переупорядочения.
Если происходит какая-то операция, например, инкримент, то атомарность уже не обеспечивается, потому что сначала выполняется чтение(1), потом изменение(2) в локальной памяти, а затем запись(3). Такая операция не является атомарной и в нее может вклиниться поток по середине.
Атомарная операция выглядит единой и неделимой командой процессора.
Переменная volatile находится в хипе, а не в кеше стека.
Для чего нужны типы данных atomic? Чем отличаются от volatile?
volatile не гарантирует атомарность. Например, операция count++ не станет атомарной просто потому, что count объявлена volatile. C другой стороны, class AtomicInteger
предоставляет атомарный метод для выполнения таких комплексных операций
атомарно, например getAndIncrement() – атомарная замена оператора инкремента, его можно использовать, чтобы атомарно увеличить текущее значение на один. Похожим образом сконструированы атомарные версии и для других типов данных.
Что-то наподобие обертки над примитивами.
Что такое потоки демоны? Для чего они нужны? Как создать поток-демон?
Потоки-демоны работают в фоновом режиме вместе с программой, но не являются неотъемлемой частью программы.


Если какой-либо процесс может выполняться на фоне работы основных потоков
выполнения и его деятельность заключается в обслуживании основных потоков
приложения, то такой процесс может быть запущен как поток-демон с помощью метода
setDaemon(boolean value), вызванного у потока до его запуска.
Метод boolean isDaemon() позволяет определить, является ли указанный поток демоном или нет. Основной поток приложения может завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода метода main(), не обращая внимания,
что поток-демон еще работает.
Поток демон можно сделать, только если он еще не запущен.
Пример демона – сборщик мусора (GC).
Что такое приоритет потока? На что он влияет? Какой приоритет у
потоков по умолчанию?
Приоритеты потоков используются планировщиком потоков для принятия решений о том,
когда и какому из потоков будет разрешено работать. Теоретически высокоприоритетные потоки получают больше времени процессора, чем низкоприоритетные. Практически объем времени процессора, который получает поток, часто зависит от нескольких факторов помимо его приоритета.
Чтобы установить приоритет потока, используется метод класса Thread: final void setPriority(int level). Значение level изменяется в пределах от Thread.MIN_PRIORITY = 1 до
Thread.MAX_PRIORITY = 10. Приоритет по умолчанию – Thread.NORM_PRlORITY = 5.
Получить текущее значение приоритета потока можно, вызвав метод: final int getPriority() у экземпляра класса Thread.
Метод yield() можно использовать для того, чтобы принудить планировщик выполнить другой поток, который ожидает своей очереди.
Как работает Thread.join()? Для чего он нужен?
Когда поток вызывает join(), он будет ждать, пока поток, к которому он присоединяется, будет завершен либо отработает переданное время:
void join()
void join(long millis) – с временем ожидания
void join(long millis, int nanos)
Применение: при распараллеливании вычисления, надо дождаться результатов, чтобы собрать их в кучу и продолжить выполнение.
Чем отличаются методы wait() и sleep()?
Метод sleep() приостанавливает поток на указанное время. Состояние меняется на
WAITING, по истечении – RUNNABLE. Монитор не освобождается.
Метод wait() меняет состояние потока на WAITING. Может быть вызван только у объекта,
владеющего блокировкой, в противном случае выкинется исключение
IllegalMonitorStateException.


Можно ли вызвать start() для одного потока дважды?
Нельзя стартовать поток больше одного раза. Поток не может быть перезапущен, если он уже завершил выполнение.
Выдает: IllegalThreadStateException.
Как правильно остановить поток? Для чего нужны методы stop(), interrupt(),
interrupted(), isInterrupted()?
Как остановить поток:
На данный момент в Java принят уведомительный порядок остановки потока (хотя JDK 1.0 и имеет несколько управляющих выполнением потока методов, например stop(), suspend() и resume() – в следующих версиях JDK все они были помечены как deprecated из-за потенциальных угроз взаимной блокировки).
1. Для корректной остановки потока можно использовать метод класса Thread interrupt().
Этот метод выставляет внутренний флаг-статус прерывания. В дальнейшем состояние этого флага можно проверить с помощью метода isInterrupted() или Thread.interrupted() (для текущего потока). Метод interrupt() способен вывести поток из состояния ожидания или
спячки. Т. е., если у потока были вызваны методы sleep() или wait(), текущее состояние прервется и будет выброшено исключение InterruptedException. Флаг в этом случае не выставляется.
Схема действия при этом получается следующей:

реализовать поток;

в потоке периодически проводить проверку статуса прерывания через вызов isInterrupted();

если состояние флага изменилось или было выброшено исключение во время ожидания/спячки, следовательно поток пытаются остановить извне;

принять решение – продолжить работу (если по каким-то причинам остановиться невозможно) или освободить заблокированные потоком ресурсы и закончить выполнение.
Возможная проблема, которая присутствует в этом подходе – блокировки на потоковом вводе-выводе. Если поток заблокирован на чтении данных – вызов interrupt() из этого состояния его не выведет. Решения тут различаются в зависимости от типа источника данных. Если чтение идет из файла, то долговременная блокировка крайне маловероятна и тогда можно просто дождаться выхода из метода read(). Если же чтение каким-то образом связано с сетью, то стоит использовать неблокирующий ввод-вывод из Java NIO.
2. Второй вариант реализации метода остановки (а также и приостановки) – сделать
собственный аналог interrupt(). Т. е. объявить в классе потока флаги на остановку и/или приостановку и выставлять их путем вызова заранее определенных методов извне.
Методика действия при этом остается прежней – проверять установку флагов и принимать решения при их изменении.
Недостатки такого подхода:

потоки в состоянии ожидания таким способом не «оживить»;

выставление флага одним потоком совсем не означает, что второй поток тут же его увидит, для увеличения производительности виртуальная машина использует кеш

данных потока, в результате чего обновление переменной у второго потока может произойти через неопределенный промежуток времени (хотя допустимым решением будет объявить переменную-флаг как volatile).
Почему не рекомендуется использовать метод Thread.stop()?
При принудительной остановке (приостановке) потока stop() прерывает поток в
недетерменированном месте выполнения, в результате становится совершенно
непонятно, что делать с принадлежащими ему ресурсами. Поток может открыть
сетевое соединение – что в таком случае делать с данными, которые еще не
вычитаны? Где гарантия, что после дальнейшего запуска потока (в случае приостановки) он сможет их дочитать? Если поток блокировал разделяемый ресурс, то как снять эту блокировку и не переведет ли принудительное снятие к нарушению консистентности системы? То же самое можно расширить и на случай соединения с базой данных: если поток остановят посередине транзакции, то кто ее будет закрывать? Кто и как будет разблокировать ресурсы?
В чем разница между interrupted() и isInterrupted()?
Механизм прерывания работы потока в Java реализован с использованием внутреннего флага, известного как статус прерывания. Прерывание потока вызовом Thread.interrupt()
устанавливает этот флаг. Методы Thread.interrupted() и isInterrupted() позволяют проверить,
является ли поток прерванным.
Когда прерванный поток проверяет статус прерывания, вызывая статический метод
Thread.interrupted(), статус прерывания сбрасывается.
Нестатический метод isInterrupted() используется одним потоком для проверки статуса прерывания у другого потока, не изменяя флаг прерывания.
Чем Runnable отличается от Callable?

интерфейс Runnable появился в Java 1.0, а интерфейс Callable был введен в Java
5.0 в составе библиотеки java.util.concurrent;

классы, реализующие интерфейс Runnable для выполнения задачи должны
реализовывать метод run(), классы, реализующие интерфейс Callable – метод
call();

метод Runnable.run() не возвращает никакого значения;

Callable – это параметризованный функциональный интерфейс, Callable.call()
возвращает Object, если он не параметризован, иначе указанный тип;

метод run() НЕ может выбрасывать проверяемые исключения, в то время как
метод call() может.
Что такое FutureTask?
FutureTask представляет собой отменяемое асинхронное вычисление в параллельном
потоке. Этот класс предоставляет базовую реализацию Future с методами для запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения результатов.
Результат может быть получен, только когда вычисление завершено, метод получения
будет заблокирован, если вычисление еще не завершено.