ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 01.12.2023
Просмотров: 486
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Объекты 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
collections – набор эффективно работающих в многопоточной среде коллекций
CopyOnWriteArrayList(Set), ConcurrentHashMap.
Итераторы классов данного пакета представляют данные на определенный момент времени.
Все операции по изменению коллекции (add, set, remove) приводят к созданию новой копии внутреннего массива. Этим гарантируется, что при проходе итератором по коллекции не будет ConcurrentModificationException.
Отличие ConcurrentHashMap связано с внутренней структурой хранения пар key-value.
СoncurrentHashMap использует несколько сегментов, и данный класс нужно рассматривать как группу HashMap’ов. Количество сегментов по умолчанию равно 16. Если пара key-value хранится в 10-ом сегменте, то ConcurrentHashMap заблокирует при необходимости только
10-й сегмент, и не будет блокировать остальные 15.
CopyOnWriteArrayList:
•
volatile массив снутри;
•
lock только при модификации списка, поэтому операции чтения очень быстрые;
•
новая копия массива при модификации;
•
fail-fast итератор;
•
модификация через iterator невозможна – UnsupportedOperationException.
synchronizers – объекты синхронизации, позволяющие разработчику управлять и/или ограничивать работу нескольких потоков. Cодержит пять объектов синхронизации:
semaphore, countDownLatch, ciclycBarrier, exchanger, phaser.
CountDownLatch – объект синхронизации потоков, блокирующий один или несколько потоков до тех пор, пока не будут выполнены определенные условия. Количество условий задается счетчиком. При обнулении счетчика, т. е. при выполнении всех условий, блокировки выполняемых потоков будут сняты и они продолжат выполнение кода. Одноразовый.
CyclicBarrier – барьерная синхронизация останавливает поток в определенном месте в ожидании прихода остальных потоков группы. Как только все потоки достигнут барьера,
барьер снимается и выполнение потоков продолжается. Как и CountDownLatch, использует счетчик и похож на него. Отличие связано с тем, барьер можно использовать повторно (в цикле).
Exchanger – объект синхронизации, используемый для двустороннего обмена данными между двумя потоками. При обмене данными допускается null-значения, что позволяет использовать класс для односторонней передачи объекта или же просто, как синхронизатор двух потоков. Обмен данными выполняется вызовом метода exchange, сопровождаемый самоблокировкой потока. Как только второй поток вызовет метод exchange, то синхронизатор
Exchanger выполнит обмен данными между потоками.
Phaser – объект синхронизации типа «Барьер», но в отличие от CyclicBarrier может иметь несколько барьеров (фаз), и количество участников на каждой фазе может быть разным.
atomic – набор атомарных классов для выполнения атомарных операций. Операция является атомарной, если ее можно безопасно выполнять при параллельных вычислениях в нескольких потоках, не используя при этом ни блокировок, ни синхронизацию synchronized.
Queues содержит классы формирования неблокирующих и блокирующих очередей для многопоточных приложений. Неблокирующие очереди «заточены» на скорость выполнения,
блокирующие очереди приостанавливают потоки при работе с очередью.
Locks – механизмы синхронизации потоков, альтернативы базовым synchronized, wait, notify,
notifyAll: Lock, Condition, ReadWriteLock.
Executors включает средства, называемые сервисами исполнения, позволяющие управлять потоковыми задачами с возможностью получения результатов через интерфейсы Future и
Callable.
ExecutorService служит альтернативой классу Thread, предназначенному для управления потоками. В основу сервиса исполнения положен интерфейс Executor, в котором определен один метод:
void execute(Runnable thread);
При вызове метода execute исполняется поток thread.
Stream API & ForkJoinPool, как связаны, что это такое?
В Stream API есть простой способ распараллеливания потока методом parallel() или
parallelStream(), чтобы получить выигрыш в производительности на многоядерных машинах.
По умолчанию parallel stream используют ForkJoinPool.commonPool. Этот пул создается статически и живет пока не будет вызван System::exit. Если задачам не указывать конкретный пул, то они будут исполняться в рамках commonPool.
По умолчанию размер пула равен на 1 меньше, чем количество доступных ядер.
Когда некий тред отправляет задачу в common pool, то пул может использовать вызывающий тред (caller-thread) в качестве воркера. ForkJoinPool пытается загрузить своими задачами и вызывающий тред.
Java Memory Model
Описывает как потоки должны взаимодействовать через общую память. Определяет набор действий межпоточного взаимодействия. В частности, чтение и запись переменной,
захват и освобождение монитора, чтение и запись volatile переменной, запуск нового потока.
JMM определяет отношение между этими действиями «happens-before» – абстракцией,
обозначающей, что если операция X связана отношением happens-before с операцией Y, то весь код следуемый за операцией Y, выполняемый в одном потоке, видит все изменения,
сделанные другим потоком до операции X.
Можно выделить несколько основных областей, имеющих отношение к модели памяти:
Видимость (visibility). Один поток может временно сохранить значения некоторых полей не в основную память, а в регистры или локальный кеш процессора, таким образом второй поток, читая из основной памяти, может не увидеть последних изменений поля. И наоборот,
если поток на протяжении какого-то времени работает с регистрами и локальными кешами,
читая данные оттуда, он может сразу не увидеть изменений, сделанных другим потоком в основную память.
К вопросу видимости имеют отношение следующие ключевые слов языка Java: synchronized,
volatile, final.
С точки зрения Java все переменные (за исключением локальных переменных, объявленных внутри метода) хранятся в heap-памяти, которая доступна всем потокам. Кроме этого каждый поток имеет локальную рабочую память, где он хранит копии переменных, с которыми он работает, и при выполнении программы поток работает только с этими копиями.