Главная страница

ОглавлениеCore


Скачать 1.53 Mb.
НазваниеОглавлениеCore
Дата17.05.2023
Размер1.53 Mb.
Формат файлаpdf
Имя файлаpolnaya_metodichka (1).pdf
ТипДокументы
#1138113
страница16 из 25
1   ...   12   13   14   15   16   17   18   19   ...   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 с методами для запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения результатов.
Результат может быть получен, только когда вычисление завершено, метод получения
будет заблокирован, если вычисление еще не завершено.

Объекты FutureTask могут быть использованы для обертки объектов Callable и Runnable. Так как FutureTask помимо Future реализует Runnable, его можно передать в Executor на выполнение.
Что такое deadlock?
Взаимная блокировка (deadlock) – явление, при котором все потоки находятся в режиме
ожидания и свое состояние не меняют. Происходит, когда достигаются состояния:

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

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

отсутствия предочистки: операционная система не переназначает ресурсы – если они уже заняты, то должны отдаваться удерживающим потокам сразу же;

цикличного ожидания: поток ждет освобождения ресурса другим потоком, который в свою очередь ждет освобождения ресурса заблокированного первым потоком.
Простейший способ избежать взаимной блокировкине допускать цикличного
ожидания. Этого можно достичь, получая мониторы разделяемых ресурсов в
определенном порядке и освобождая их в обратном порядке.
Что такое livelock?
livelock – тип взаимной блокировки, при котором несколько потоков выполняют
бесполезную работу, попадая в зацикленность при попытке получения каких-либо
ресурсов. При этом их состояния постоянно изменяются в зависимости друг от друга.
Фактической ошибки не возникает, но КПД системы падает до 0. Часто возникает в результате попыток предотвращения deadlock.
Узнать о наличии livelock можно, например, проверив уровень загрузки процессора в состоянии покоя.
Примеры livelock:

2 человека встречаются в узком коридоре и каждый, пытаясь быть вежливым, отходит в сторону, и так они бесконечно двигаются из стороны в сторону, абсолютно не продвигаясь в нужном им направлении;

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

на равнозначном перекрестке 4 автомобиля не могут определить, кто из них должен уступить дорогу;

одновременный звонок друг другу.
Что такое race condition?
Состояние гонки (race condition) – ошибка проектирования многопоточной системы или
приложения, при которой работа зависит от того, в каком порядке выполняются
потоки. Состояние гонки возникает, когда поток, который должен исполниться в начале,
проиграл гонку, и первым исполняется другой поток: поведение кода изменяется, из-за чего возникают недетерменированные ошибки.

DataRace – это свойство выполнения программы. Согласно JMM, выполнение считается содержащим гонку данных, если оно содержит по крайней мере два конфликтующих доступа
(чтение или запись в одну и ту же переменную), которые не упорядочены отношениями
«happens before».
Starvation – потоки не заблокированы, но есть нехватка ресурсов, из-за чего потоки ничего не делают.
Самый простой способ решения – копирование переменной в локальную переменную. Или просто синхронизация потоков методами и synchronized-блоками.
Что такое Фреймворк fork/join? Для чего он нужен?
Фреймворк Fork/Join, представленный в JDK 7, – это набор классов и интерфейсов,
позволяющих использовать преимущества многопроцессорной архитектуры современных компьютеров. Он разработан для выполнения задач, которые можно рекурсивно
разбить на маленькие подзадачи, которые можно решать параллельно.
Этап Fork: большая задача разделяется на несколько меньших подзадач, которые в свою очередь также разбиваются на меньшие. И так до тех пор, пока задача не
становится тривиальной и решаемой последовательным способом.
Этап Join: далее (опционально) идет процесс «свертки» – решения подзадач некоторым
образом объединяются, пока не получится решение всей задачи.
Решение всех подзадач (в т. ч. и само разбиение на подзадачи) происходит параллельно.
Для решения некоторых задач этап Join не требуется. Например, для параллельного
QuickSort – массив рекурсивно делится на все меньшие и меньшие диапазоны, пока не вырождается в тривиальный случай из 1 элемента. Хотя в некотором смысле Join будет необходим и тут, т. к. все равно остается необходимость дождаться, пока не закончится выполнение всех подзадач.
Еще одно преимущество этого фреймворка заключается в том, что он использует work-
stealing алгоритм: потоки, которые завершили выполнение собственных подзадач, могут
«украсть» подзадачи у других потоков, которые все еще заняты.
Что означает ключевое слово synchronized? Где и для чего может
использоваться?
Зарезервированное слово позволяет добиваться синхронизации в помеченных им методах
или блоках кода.
Что является монитором у статического synchronized-метода?
Объект типа Class, соответствующий классу, в котором определен метод.
Что является монитором у нестатического synchronized-метода?
Объект this.
util.Concurrent поверхностно
Классы и интерфейсы пакета java.util.concurrent объединены в несколько групп по функциональному признаку:

1   ...   12   13   14   15   16   17   18   19   ...   25


написать администратору сайта