diff --git a/concurrency.md b/concurrency.md index e66f8f3..9e21830 100644 --- a/concurrency.md +++ b/concurrency.md @@ -1,6 +1,6 @@ [Вопросы для собеседования на Java Junior](README.md) -#Многопоточность +# Многопоточность + [Расскажите о модели памяти Java?](#Расскажите-о-модели-памяти-java) + [Что такое «потокобезопасность»?](#Что-такое-потокобезопасность) + [Что такое _«кооперативная многозадачность»_? Какой тип многозадачности использует Java? Чем обусловлен этот выбор?](#Что-такое-кооперативная-многозадачность-Какой-тип-многозадачности-использует-java-Чем-обусловлен-этот-выбор) @@ -71,7 +71,7 @@ + [Напишите простейший многопоточный ограниченный буфер с использованием `synchronized`.](#Напишите-простейший-многопоточный-ограниченный-буфер-с-использованием-synchronized) + [Напишите простейший многопоточный ограниченный буфер с использованием `ReentrantLock`.](#Напишите-простейший-многопоточный-ограниченный-буфер-с-использованием-reentrantlock) -##Расскажите о модели памяти Java? +## Расскажите о модели памяти Java? __Модель памяти Java (Java Memory Model, JMM)__ описывает поведение потоков в среде исполнения Java. Это часть семантики языка Java, набор правил, описывающий выполнение многопоточных программ и правил, по которым потоки могут взаимодействовать друг с другом посредством основной памяти. Формально модель памяти определяет набор действий межпоточного взаимодействия (эти действия включают в себя, в частности, чтение и запись переменной, захват и освобождений монитора, чтение и запись volatile переменной, запуск нового потока), а также модель памяти определяет отношение между этими действиями -_happens-before_ - абстракции обозначающей, что если операция _X_ связана отношением happens-before с операцией _Y_, то весь код следуемый за операцией _Y_, выполняемый в одном потоке, видит все изменения, сделанные другим потоком, до операции _X_. @@ -112,12 +112,12 @@ _Reordering (переупорядочивание)_. Для увеличения [к оглавлению](#Многопоточность) -##Что такое «потокобезопасность»? +## Что такое «потокобезопасность»? Потокобезопасность – свойство объекта или кода, которое гарантирует, что при исполнении или использовании несколькими потоками, код будет вести себя, как предполагается. Например потокобезопасный счётчик не пропустит ни один счёт, даже если один и тот же экземпляр этого счётчика будет использоваться несколькими потоками. [к оглавлению](#Многопоточность) -##Что такое _«кооперативная многозадачность»_? Какой тип многозадачности использует Java? Чем обусловлен этот выбор? +## Что такое _«кооперативная многозадачность»_? Какой тип многозадачности использует Java? Чем обусловлен этот выбор? __Кооперативная многозадачность__ - это способ деления процессорного времени между потоками, при котором каждый поток обязан отдавать управление следующему добровольно. @@ -133,7 +133,7 @@ Java использует __вытесняющую многозадачност [к оглавлению](#Многопоточность) -##Что такое _ordering_, _as-if-serial semantics_, _sequential consistency_, _visibility_, _atomicity_, _happens-before_, _mutual exclusion_, _safe publication_? +## Что такое _ordering_, _as-if-serial semantics_, _sequential consistency_, _visibility_, _atomicity_, _happens-before_, _mutual exclusion_, _safe publication_? __ordering__ механизм, который определяет, когда один поток может увидеть _out-of-order_ (неверный) порядок исполнения инструкций другого потока. CPU для для повышения производительности может переупорядочивать процессорные инструкции и выполнять их в произвольном порядке до тех пор пока для потока внутри не будет видно никаких отличий. Гарантия предоставляемая этим механизмом называется __as-if-serial semantics__. __sequential consistency__ - то же что и _as-if-serial semantics_, гарантия того, что в рамках одного потока побочные эффекты от всех операций будут такие, как будто все операции выполняются последовательно. @@ -156,7 +156,7 @@ __safe publication__? - показ объектов другим потокам [к оглавлению](#Многопоточность) -##Чем отличается процесс от потока? +## Чем отличается процесс от потока? __Процесс__ — экземпляр программы во время выполнения, независимый объект, которому выделены системные ресурсы (например, процессорное время и память). Каждый процесс выполняется в отдельном адресном пространстве: один процесс не может получить доступ к переменным и структурам данных другого. Если процесс хочет получить доступ к чужим ресурсам, необходимо использовать межпроцессное взаимодействие. Это могут быть конвейеры, файлы, каналы связи между компьютерами и многое другое. Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память. @@ -167,7 +167,7 @@ __Поток__(thread) — определенный способ выполне [к оглавлению](#Многопоточность) -##Что такое _«зелёные потоки»_ и есть ли они в Java? +## Что такое _«зелёные потоки»_ и есть ли они в Java? __Зелёные (легковесные) потоки__(green threads) - потоки эмулируемые виртуальной машиной или средой исполнения. Создание зелёного потока не подразумевает под собой создание реального потока ОС. Виртуальная машина Java берёт на себя заботу о переключении между разными green threads, а сама машина работает как один поток ОС. Это даёт несколько преимуществ. Потоки ОС относительно дороги в большинстве POSIX-систем. Кроме того, переключение между native threads гораздо медленнее, чем между green threads. @@ -180,14 +180,14 @@ __Зелёные (легковесные) потоки__(green threads) - пот [к оглавлению](#Многопоточность) -##Каким образом можно создать поток? +## Каким образом можно создать поток? + Создать потомка класса `Thread` и переопределить его метод `run()`; + Создать объект класса `Thread`, передав ему в конструкторе экземпляр класса, реализующего интерфейс `Runnable`. Эти интерфейс содержит метод `run()`, который будет выполняться в новом потоке. Поток закончит выполнение, когда завершится его метод `run()`. + Вызвать метод `submit()` у экземпляра класса реализующего интерфейс `ExecutorService`, передав ему в качестве параметра экземпляр класса реализующего интерфейс `Runnable` или `Callable` (содержит метод `call()`, в котором описывается логика выполнения). [к оглавлению](#Многопоточность) -##Чем различаются `Thread` и `Runnable`? +## Чем различаются `Thread` и `Runnable`? `Thread` - это класс, некоторая надстройка над физическим потоком. `Runnable` - это интерфейс, представляющий абстракцию над выполняемой задачей. @@ -196,17 +196,17 @@ __Зелёные (легковесные) потоки__(green threads) - пот [к оглавлению](#Многопоточность) -##В чём заключается разница между методами `start()` и `run()`? +## В чём заключается разница между методами `start()` и `run()`? Несмотря на то, что `start()` вызывает метод `run()` внутри себя, это не то же самое, что просто вызов `run()`. Если `run()` вызывается как обычный метод, то он вызывается в том же потоке и никакой новый поток не запускается, как это происходит, в случае, когда вы вызываете метод `start()`. [к оглавлению](#Многопоточность) -##Как принудительно запустить поток? +## Как принудительно запустить поток? Никак. В Java не существует абсолютно никакого способа принудительного запуска потока. Это контролируется JVM и Java не предоставляет никакго API для управления этим процессом. [к оглавлению](#Многопоточность) -##Что такое _«монитор»_ в Java? +## Что такое _«монитор»_ в Java? __Монитор__, мьютекс (mutex) – это средство обеспечения контроля за доступом к ресурсу. У монитора может быть максимум один владелец в каждый текущий момент времени. Следовательно, если кто-то использует ресурс и захватил монитор для обеспечения единоличного доступа, то другой, желающий использовать тот же ресурс, должен подождать освобождения монитора, захватить его и только потом начать использовать ресурс. Удобно представлять монитор как id захватившего его объекта. Если этот id равен 0 – ресурс свободен. Если не 0 – ресурс занят. Можно встать в очередь и ждать его освобождения. @@ -215,14 +215,14 @@ __Монитор__, мьютекс (mutex) – это средство обес [к оглавлению](#Многопоточность) -##Дайте определение понятию «синхронизация». +## Дайте определение понятию «синхронизация». Синхронизация это процесс, который позволяет выполнять потоки параллельно. В Java все объекты имеют одну блокировку, благодаря которой только один поток одновременно может получить доступ к критическому коду в объекте. Такая синхронизация помогает предотвратить повреждение состояния объекта. Если поток получил блокировку, ни один другой поток не может войти в синхронизированный код, пока блокировка не будет снята. Когда поток, владеющий блокировкой, выходит из синхронизированного кода, блокировка снимается. Теперь другой поток может получить блокировку объекта и выполнить синхронизированный код. Если поток пытается получить блокировку объекта, когда другой поток владеет блокировкой, поток переходит в состояние Блокировки до тех пор, пока блокировка не снимется. [к оглавлению](#Многопоточность) -##Какие существуют способы синхронизации в Java? +## Какие существуют способы синхронизации в Java? + __Системная синхронизация с использованием `wait()`/`notify()`__. Поток, который ждет выполнения каких-либо условий, вызывает у этого объекта метод `wait()`, предварительно захватив его монитор. На этом его работа приостанавливается. Другой поток может вызвать на этом же самом объекте метод `notify()` (опять же, предварительно захватив монитор объекта), в результате чего, ждущий на объекте поток «просыпается» и продолжает свое выполнение. В обоих случаях монитор надо захватывать в явном виде, через `synchronized`-блок, потому как методы `wait()`/`notify()` не синхронизированы! + __Системная синхронизация с использованием `join()`__. Метод `join()`, вызванный у экземпляра класса `Thread`, позволяет текущему потоку остановиться до того момента, как поток, связаный с этим экземпляром, закончит работу. @@ -231,7 +231,7 @@ __Монитор__, мьютекс (mutex) – это средство обес [к оглавлению](#Многопоточность) -##В каких состояниях может находиться поток? +## В каких состояниях может находиться поток? Потоки могут находиться в одном из следующих состояний: + __Новый (New)__. После создания экземпляра потока, он находится в состоянии Новый до тех пор, пока не вызван метод `start()`. В этом состоянии поток не считается живым. @@ -245,17 +245,17 @@ __Монитор__, мьютекс (mutex) – это средство обес [к оглавлению](#Многопоточность) -##Можно ли создавать новые экземпляры класса, пока выполняется `static synchronized` метод? +## Можно ли создавать новые экземпляры класса, пока выполняется `static synchronized` метод? Да, можно создавать новые экземпляры класса, так как статические поля не принадлежат к экземплярам класса. [к оглавлению](#Многопоточность) -##Зачем может быть нужен `private` мьютекс? +## Зачем может быть нужен `private` мьютекс? Объект для синхронизации делается `private`, чтобы сторонний код не мог на него синхронизироваться и случайно получить взаимную блкировку. [к оглавлению](#Многопоточность) -##Как работают методы `wait()` и `notify()`/`notifyAll()`? +## Как работают методы `wait()` и `notify()`/`notifyAll()`? Эти методы поределены у класса `Object` и предназначены для взаимодействия потоков между собой при межпоточной синхронизации. + `wait()`: освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод `notify()`/`notifyAll()`; @@ -268,17 +268,17 @@ __Монитор__, мьютекс (mutex) – это средство обес [к оглавлению](#Многопоточность) -##В чем разница между `notify()` и `notifyAll()`? +## В чем разница между `notify()` и `notifyAll()`? Дело в том, что «висеть» на методе `wait()` одного монитора могут сразу несколько потоков. При вызове `notify()` только один из них выходит из `wait()` и пытается захватить монитор, а затем продолжает работу со следующего после `wait()` оператора. Какой из них выйдет - заранее неизвестно. А при вызове `notifyAll()`, все висящие на `wait()` потоки выходят из `wait()`, и все они пытаются захватить монитор. Понятно, что в любой момент времени монитор может быть захвачен только одним потоком, а остальные ждут своей очереди. Порядок очереди определяется планировщиком потоков Java. [к оглавлению](#Многопоточность) -##Почему методы `wait()` и `notify()` вызываются только в синхронизированном блоке? +## Почему методы `wait()` и `notify()` вызываются только в синхронизированном блоке? Монитор надо захватывать в явном виде (через `synchronized`-блок), потому что методы `wait()` и `notify()` не синхронизированы. [к оглавлению](#Многопоточность) -##Чем отличается работа метода `wait()` с параметром и без параметра? +## Чем отличается работа метода `wait()` с параметром и без параметра? `wait()` + __без параметров__ освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод `notify()`/`notifyAll()`, @@ -286,14 +286,14 @@ __Монитор__, мьютекс (mutex) – это средство обес [к оглавлению](#Многопоточность) -##Чем отличаются методы `Thread.sleep()` и `Thread.yield()`? +## Чем отличаются методы `Thread.sleep()` и `Thread.yield()`? Метод `yield()` служит причиной того, что поток переходит из состояния работающий (running) в состояние работоспособный (runnable), давая возможность другим потокам активизироваться. Но следующий выбранный для запуска поток может и не быть другим. Метод `sleep()` вызывает засыпание текущего потока на заданное время, состояние изменяется с работающий (running) на ожидающий (waiting). [к оглавлению](#Многопоточность) -##Как работает метод `Thread.join()`? +## Как работает метод `Thread.join()`? Когда поток вызывает `join()` для другого потока, текущий работающий поток будет ждать, пока другой поток, к которому он присоединяется, не будет завершён: ```java @@ -304,7 +304,7 @@ void join(long millis, int nanos) [к оглавлению](#Многопоточность) -##Что такое _deadlock_? +## Что такое _deadlock_? __Взаимная блокировка (deadlock)__ - явление при котором все потоки находятся в режиме ожидания. Происходит, когда достигаются состояния: 1. взаимного исключения: по крайней мере один ресурс занят в режиме неделимости и следовательно только один поток может использовать ресурс в любой данный момент времени. @@ -316,19 +316,19 @@ __Взаимная блокировка (deadlock)__ - явление при к [к оглавлению](#Многопоточность) -##Что такое _livelock_? +## Что такое _livelock_? _livelock_ – тип взаимной блокировки, при котором несколько потоков выполняют бесполезную работу, попадая в зацикленность при попытке получения каких-либо ресурсов. При этом их состояния постоянно изменяются в зависимости друг от друга. Фактической ошибки не возникает, но КПД системы падает до 0. Часто возникает в результате попыток предотвращения deadlock. > Реальный пример livelock, – когда два человека встречаются в узком коридоре и каждый, пытаясь быть вежливым, отходит в сторону, и так они бесконечно двигаются из стороны в сторону, абсолютно не продвигаясь в нужном им направлении. [к оглавлению](#Многопоточность) -##Как проверить, удерживает ли поток монитор определённого ресурса? +## Как проверить, удерживает ли поток монитор определённого ресурса? Метод `Thread.holdsLock(lock)` возвращает `true`, когда текущий поток удерживает монитор у определённого объекта. [к оглавлению](#Многопоточность) -##На каком объекте происходит синхронизация при вызове `static synchronized` метода? +## На каком объекте происходит синхронизация при вызове `static synchronized` метода? У синхронизированного статического метода нет доступа к `this`, но есть доступ к объекту класса `Class`, он присутствует в единственном экземпляре и именно он выступает в качестве монитора для синхронизации статических методов. Таким образом, следующая конструкция: ```java @@ -355,7 +355,7 @@ public class SomeClass { [к оглавлению](#Многопоточность) -##Для чего используется ключевое слово `volatile`, `synchronized`, `transient`, `native`? +## Для чего используется ключевое слово `volatile`, `synchronized`, `transient`, `native`? __`volatile`__ - этот модификатор вынуждает потоки отключить оптимизацию доступа и использовать единственный экземпляр переменной. Если переменная примитивного типа – этого будет достаточно для обеспечения потокобезопасности. Если же переменная является ссылкой на объект – синхронизировано будет исключительно значение этой ссылки. Все же данные, содержащиеся в объекте, синхронизированы не будут! __`synchronized`__ - это зарезервированное слово позволяет добиваться синхронизации в помеченных им методах или блоках кода. @@ -364,19 +364,19 @@ __`synchronized`__ - это зарезервированное слово поз [к оглавлению](#Многопоточность) -##В чём различия между `volatile` и _Atomic_ переменными? +## В чём различия между `volatile` и _Atomic_ переменными? `volatile` принуждает использовать единственный экземпляр переменной, но не гарантирует атомарность. Например, операция `count++` не станет атомарной просто потому что `count` объявлена `volatile`. C другой стороны `class AtomicInteger` предоставляет атомарный метод для выполнения таких комплексных операций атомарно, например `getAndIncrement()` – атомарная замена оператора инкремента, его можно использовать, чтобы атомарно увеличить текущее значение на один. Похожим образом сконструированы атомарные версии и для других типов данных. [к оглавлению](#Многопоточность) -## В чём заключаются различия между `java.util.concurrent.Atomic*.compareAndSwap()` и `java.util.concurrent.Atomic*.weakCompareAndSwap()`. +## В чём заключаются различия между `java.util.concurrent.Atomic*.compareAndSwap()` и `java.util.concurrent.Atomic*.weakCompareAndSwap()`. + `weakCompareAndSwap()` не создает _memory barrier_ и не дает гарантии _happens-before_; + `weakCompareAndSwap()` сильно зависит от кэша/CPU, и может возвращать `false` без видимых причин; + `weakCompareAndSwap()`, более легкая, но поддерживаемая далеко не всеми архитектурами и не всегда эффективная операция. [к оглавлению](#Многопоточность) -##Что значит _«приоритет потока»_? +## Что значит _«приоритет потока»_? Приоритеты потоков используются планировщиком потоков для принятия решений о том, когда какому из потоков будет разрешено работать. Теоретически высокоприоритетные потоки получают больше времени процессора, чем низкоприоритетные. Практически объем времени процессора, который получает поток, часто зависит от нескольких факторов помимо его приоритета. Чтобы установить приоритет потока, используется метод класса `Thread`: `final void setPriority(int level)`. Значение `level` изменяется в пределах от `Thread.MIN_PRIORITY = 1` до `Thread.MAX_PRIORITY = 10`. Приоритет по умолчанию - `Thread.NORM_PRlORITY = 5`. @@ -385,22 +385,22 @@ __`synchronized`__ - это зарезервированное слово поз [к оглавлению](#Многопоточность) -##Что такое _«потоки-демоны»_? +## Что такое _«потоки-демоны»_? Потоки-демоны работают в фоновом режиме вместе с программой, но не являются неотъемлемой частью программы. Если какой-либо процесс может выполняться на фоне работы основных потоков выполнения и его деятельность заключается в обслуживании основных потоков приложения, то такой процесс может быть запущен как поток-демон с помощью метода `setDaemon(boolean value)`, вызванного у потока до его запуска. Метод `boolean isDaemon()` позволяет определить, является ли указанный поток демоном или нет. Базовое свойство потоков-демонов заключается в возможности основного потока приложения завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода метода `main()`, не обращая внимания на то, что поток-демон еще работает. [к оглавлению](#Многопоточность) -##Можно ли сделать основной поток программы демоном? +## Можно ли сделать основной поток программы демоном? Нет. Потоки-демоны позволяют описывать фоновые процессы, которые нужны только для обслуживания основных потоков выполнения и не могут существовать без них. [к оглавлению](#Многопоточность) -##Что значит _«усыпить»_ поток? +## Что значит _«усыпить»_ поток? Это значит приостановить его на определенный промежуток времени, вызвав в ходе его выполнения статический метод `Thread.sleep()` передав в качестве параметра необходимое количество времени в миллисекундах. До истечения этого времени поток может быть выведен из состояния ожидания вызовом `interrupt()` с выбрасыванием `InterruptedException`. [к оглавлению](#Многопоточность) -##Чем отличаются два интерфейса `Runnable` и `Callable`? +## Чем отличаются два интерфейса `Runnable` и `Callable`? + Интерфейс `Runnable` появиля в Java 1.0, а интерфейс `Callable` был введен в Java 5.0 в составе библиотеки `java.util.concurrent`; + Классы, реализующие интерфейс `Runnable` для выполнения задачи должны реализовывать метод `run()`. Классы, реализующие интерфейс `Callable` - метод `call()`; + Метод `Runnable.run()` не возвращает никакого значения, `Callable.call()` возвращает объект `Future`, который может содержать результат вычислений; @@ -408,12 +408,12 @@ __`synchronized`__ - это зарезервированное слово поз [к оглавлению](#Многопоточность) -##Что такое `FutureTask`? +## Что такое `FutureTask`? `FutureTask` представляет собой отменяемое асинхронное вычисление в параллельном Java приложении. Этот класс предоставляет базовую реализацию `Future`, с методами для запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения результатов. Результат может быть получен только когда вычисление завершено, метод получения будет заблокирован, если вычисление ещё не завершено. Объекты `FutureTask` могут быть использованы для обёртки объектов `Callable` и `Runnable`. Так как `FutureTask` реализует `Runnable`, его можно передать в `Executor` на выполнение. [к оглавлению](#Многопоточность) -##В чем заключаются различия между `CyclicBarrier` и `CountDownLatch`? +## В чем заключаются различия между `CyclicBarrier` и `CountDownLatch`? `CountDownLatch` (замок с обратным отсчетом) предоставляет возможность любому количеству потоков в блоке кода ожидать до тех пор, пока не завершится определенное количество операций, выполняющихся в других потоках, перед тем как они будут «отпущены», чтобы продолжить свою деятельность. В конструктор `CountDownLatch(int count)` обязательно передается количество операций, которое должно быть выполнено, чтобы замок «отпустил» заблокированные потоки. > Примером `CountDownLatch` из жизни может служить сбор экскурсионной группы: пока не наберется определенное количество человек, экскурсия не начнется. @@ -426,12 +426,12 @@ __`synchronized`__ - это зарезервированное слово поз [к оглавлению](#Многопоточность) -##Что такое _race condition_? +## Что такое _race condition_? __Состояние гонки__ (race condition) - ошибка проектирования многопоточной системы или приложения, при которой эта работа напрямую зависит от того, в каком порядке выполняются потоки. Состояние гонки возникает когда поток, который должен исполнится в начале, проиграл гонку и первым исполняется другой поток: поведение кода изменяется, из-за чего возникают недетерменированные ошибки. [к оглавлению](#Многопоточность) -##Существует ли способ решения проблемы _race condition_? +## Существует ли способ решения проблемы _race condition_? Распространённые способы решения: + __Использование локальной копии__ — копирование разделяемой переменной в локальную переменную потока. Этот способ работает только тогда, когда переменная одна и копирование производится атомарно (за одну машинную команду), использование `volatile`. @@ -442,7 +442,7 @@ __Состояние гонки__ (race condition) - ошибка проекти [к оглавлению](#Многопоточность) -##Как остановить поток? +## Как остановить поток? На данный момент в Java принят уведомительный порядок остановки потока (хотя JDK 1.0 и имеет несколько управляющих выполнением потока методов, например `stop()`, `suspend()` и `resume()` - в следующих версиях JDK все они были помечены как `deprecated` из-за потенциальных угроз взаимной блокировки). Для корректной остановки потока можно использовать метод класса `Thread` - `interrupt()`. Этот метод выставляет некоторый внутренний флаг-статус прерывания. В дальнейшем состояние этого флага можно проверить с помощью метода `isInterrupted()` или `Thread.interrupted()` (для текущего потока). Метод `interrupt()` также способен вывести поток из состояния ожидания или спячки. Т.е. если у потока были вызваны методы `sleep()` или `wait()` – текущее состояние прервется и будет выброшено исключение `InterruptedException`. Флаг в этом случае не выставляется. @@ -460,18 +460,18 @@ __Состояние гонки__ (race condition) - ошибка проекти [к оглавлению](#Многопоточность) -##Почему не рекомендуется использовать метод `Thread.stop()`? +## Почему не рекомендуется использовать метод `Thread.stop()`? При принудительной остановке (приостановке) потока, `stop()` прерывает поток в недетерменированном месте выполнения, в результате становится совершенно непонятно, что делать с принадлежащими ему ресурсами. Поток может открыть сетевое соединение - что в таком случае делать с данными, которые еще не вычитаны? Где гарантия, что после дальнейшего запуска потока (в случае приостановки) он сможет их дочитать? Если поток блокировал разделяемый ресурс, то как снять эту блокировку и не переведёт ли принудительное снятие к нарушению консистентности системы? То же самое можно расширить и на случай соединения с базой данных: если поток остановят посередине транзакции, то кто ее будет закрывать? Кто и как будет разблокировать ресурсы? [к оглавлению](#Многопоточность) -##Что происходит, когда в потоке выбрасывается исключение? +## Что происходит, когда в потоке выбрасывается исключение? + Если исключение не поймано – поток «умирает» (переходит в состяние мёртв (dead)). + Если установлен обработчик непойманных исключений, то он возьмёт управление на себя. `Thread.UncaughtExceptionHandler` – интерфейс, определённый как вложенный интерфейс для других обработчиков, вызываемых, когда поток внезапно останавливается из-за непойманного исключения. В случае, если поток собирается остановиться из-за непойманного исключения, JVM проверяет его на наличие `UncaughtExceptionHandler`, используя `Thread.getUncaughtExceptionHandler()`, и если такой обработчик найдет, то вызовет у него метод `uncaughtException()`, передав этот поток и исключение в виде аргументов. [к оглавлению](#Многопоточность) -##В чем разница между `interrupted()` и `isInterrupted()`? +## В чем разница между `interrupted()` и `isInterrupted()`? Механизм прерывания работы потока в Java реализован с использованием внутреннего флага, известного как статус прерывания. Прерывание потока вызовом `Thread.interrupt()` устанавливает этот флаг. Методы `Thread.interrupted()` и `isInterrupted()` позволяют проверить, является ли поток прерванным. Когда прерванный поток проверяет статус прерывания, вызывая статический метод `Thread.interrupted()`, статус прерывания сбрасывается. @@ -480,7 +480,7 @@ __Состояние гонки__ (race condition) - ошибка проекти [к оглавлению](#Многопоточность) -##Что такое _«пул потоков»_? +## Что такое _«пул потоков»_? Создание потока является затратной по времени и ресурсам операцией. Количество потоков, которое может быть запущено в рамках одного процесса также ограниченно. Чтобы избежать этих проблем и в целом управлять множеством потоков более эффективно в Java был реализован механизм пула потоков (thread pool), который создаётся во время запуска приложения и в дальнейшем потоки для обработки запросов берутся и переиспользуются уже из него. Таким образом, появляется возможность не терять потоки, сбалансировать приложение по количеству потоков и частоте их создания. Начиная с Java 1.5 Java API предоставляет фреймворк `Executor`, который позволяет создавать различные типы пула потоков: @@ -507,7 +507,7 @@ __Состояние гонки__ (race condition) - ошибка проекти [к оглавлению](#Многопоточность) -##Какого размера должен быть пул потоков? +## Какого размера должен быть пул потоков? Настраивая размер пула потоков, важно избежать двух ошибок: слишком мало потоков (очередь на выполнение будет расти, потребляя много памяти) или слишком много потоков (замедение работы всей систему из-за частых переключений контекста). Оптимальный размер пула потоков зависит от количества доступных процессоров и природы задач в рабочей очереди. На N-процессорной системе для рабочей очереди, которая будет выполнять исключительно задачи с ограничением по скорости вычислений, можно достигнуть максимального использования CPU с пулом потоков, в котором содержится N или N+1 поток. @@ -517,12 +517,12 @@ __Состояние гонки__ (race condition) - ошибка проекти [к оглавлению](#Многопоточность) -##Что будет, если очередь пула потоков уже заполнена, но подаётся новая задача? +## Что будет, если очередь пула потоков уже заполнена, но подаётся новая задача? Если очередь пула потоков заполнилась, то поданная задача будет «отклонена». Например - метод `submit()` у `ThreadPoolExecutor` выкидывает `RejectedExecutionException`, после которого вызывается `RejectedExecutionHandler`. [к оглавлению](#Многопоточность) -##В чём заключается различие между методами `submit()` и `execute()` у пула потоков? +## В чём заключается различие между методами `submit()` и `execute()` у пула потоков? Оба метода являются способами подачи задачи в пул потоков, но между ними есть небольшая разница. `execute(Runnable command)` определён в интерфейсе `Executor` и выполняет поданную задачу и ничего не возвращает. @@ -531,24 +531,24 @@ __Состояние гонки__ (race condition) - ошибка проекти [к оглавлению](#Многопоточность) -##В чем заключаются различия между cтеком (stack) и кучей (heap) с точки зрения многопоточности? +## В чем заключаются различия между cтеком (stack) и кучей (heap) с точки зрения многопоточности? __Cтек__ – участок памяти, тесно связанный с потоками. У каждого потока есть свой стек, которые хранит локальные переменные, параметры методов и стек вызовов. Переменная, хранящаяся в стеке одного потока, не видна для другого. __Куча__ – общий участок памяти, который делится между всеми потоками. Объекты, неважно локальные или любого другого уровня, создаются в куче. Для улучшения производительности, поток обычно кэширует значения из кучи в свой стек, в этом случае для того, чтобы указать потоку, что переменную следует читать из кучи используется ключевое слово `volatile`. [к оглавлению](#Многопоточность) -##Как поделиться данными между двумя потоками? +## Как поделиться данными между двумя потоками? Данными между потоками возможно делиться, используя общий объект или параллельные структуры данных, например `BlockingQueue`. [к оглавлению](#Многопоточность) -##Какой параметр запуска JVM используется для контроля размера стека потока? +## Какой параметр запуска JVM используется для контроля размера стека потока? `-Xss` [к оглавлению](#Многопоточность) -##Как получить дамп потока? +## Как получить дамп потока? Среды исполнения Java на основе HotSpot генерируют только дамп в формате HPROF. В распоряжении разработчика имеется несколько интерактивных методов генерации дампов и один метод генерации дампов на основе событий. Интерактивные методы: @@ -565,7 +565,7 @@ __Куча__ – общий участок памяти, который дели [к оглавлению](#Многопоточность) -##Что такое _ThreadLocal-переменная_? +## Что такое _ThreadLocal-переменная_? `ThreadLocal` - класс, позволяющий имея одну переменную, иметь различное её значение для каждого из потоков. У каждого потока - т.е. экземпляра класса `Thread` - есть ассоциированная с ним таблица _ThreadLocal-переменных_. Ключами таблицы являются cсылки на объекты класса `ThreadLocal`, а значениями - ссылки на объекты, «захваченные» ThreadLocal-переменными, т.е. ThreadLocal-переменные отличаются от обычных переменных тем, что у каждого потока свой собственный, индивидуально инициализируемый экземпляр переменной. Доступ к значению можно получить через методы `get()` или `set()`. @@ -578,7 +578,7 @@ __Куча__ – общий участок памяти, который дели [к оглавлению](#Многопоточность) -##Назовите различия между `synchronized` и `ReentrantLock`? +## Назовите различия между `synchronized` и `ReentrantLock`? В Java 5 появился интерфейс `Lock` предоставляющий возможности более эффективного и тонкого контроля блокировки ресурсов. `ReentrantLock` – распространённая реализация `Lock`, которая предоставляет `Lock` с таким же базовым поведением и семантикой, как у `synchronized`, но расширенными возможностями, такими как опрос о блокировании (lock polling), ожидание блокирования заданной длительности и прерываемое ожидание блокировки. Кроме того, он предлагает гораздо более высокую эффективность функционирования в условиях жесткой _состязательности_. Что понимается под блокировкой с повторным входом (reentrant)? Просто то, что есть подсчет сбора данных, связанный с блокировкой, и если поток, который удерживает блокировку, снова ее получает, данные отражают увеличение, и тогда для реального разблокирования нужно два раза снять блокировку. Это аналогично семантике synchronized; если поток входит в синхронный блок, защищенный монитором, который уже принадлежит потоку, потоку будет разрешено дальнейшее функционирование, и блокировка не будет снята, когда поток выйдет из второго (или последующего) блока synchronized, она будет снята только когда он выйдет из первого блока synchronized, в который он вошел под защитой монитора. @@ -602,7 +602,7 @@ finally { [к оглавлению](#Многопоточность) -##Что такое `ReadWriteLock`? +## Что такое `ReadWriteLock`? `ReadWriteLock` – это интерфейс расширяющий базовый интерфейс `Lock`. Используется для улучшения производительности в многопоточном процессе и оперирует парой связанных блокировок (одна - для операций чтения, другая - для записи). Блокировка чтения может удерживаться одновременно несколькими читающими потоками, до тех пор пока не появится записывающий. Блокировка записи является эксклюзивеной. Существует реализующий интерфейс `ReadWriteLock` класс `ReentrantReadWriteLock`, который поддерживает до 65535 блокировок записи и до стольки же блокировок чтения. @@ -629,12 +629,12 @@ try { [к оглавлению](#Многопоточность) -##Что такое _«блокирующий метод»_? +## Что такое _«блокирующий метод»_? __Блокирующий метод__ – метод, который блокируется, до тех пор, пока задание не выполнится, например метод `accept()` у `ServerSocket` блокируется в ожидании подключения клиента. Здесь блокирование означает, что контроль не вернётся к вызывающему методу до тех пор, пока не выполнится задание. Так же существуют асинхронные или неблокирующиеся методы, которые могут завершится до выполнения задачи. [к оглавлению](#Многопоточность) -##Что такое _«фреймворк Fork/Join»_? +## Что такое _«фреймворк Fork/Join»_? Фреймворк Fork/Join, представленный в JDK 7, - это набор классов и интерфейсов позволяющих использовать преимущества многопроцессорной архитектуры современных компьютеров. Он разработан для выполнения задач, которые можно рекурсивно разбить на маленькие подзадачи, которые можно решать параллельно. + Этап Fork: большая задача разделяется на несколько меньших подзадач, которые в свою очередь также разбиваются на меньшие. И так до тех пор, пока задача не становится тривиальной и решаемой последовательным способом. @@ -648,12 +648,12 @@ __Блокирующий метод__ – метод, который блоки [к оглавлению](#Многопоточность) -##Что такое `Semaphore`? +## Что такое `Semaphore`? Semaphore – это новый тип синхронизатора: семафор со счётчиком, реализующий шаблон синхронизации Семафор. Доступ управляется с помощью счётчика: изначальное значение счётчика задаётся в конструкторе при создании синхронизатора, когда поток заходит в заданный блок кода, то значение счётчика уменьшается на единицу, когда поток его покидает, то увеличивается. Если значение счётчика равно нулю, то текущий поток блокируется, пока кто-нибудь не выйдет из защищаемого блока. Semaphore используется для защиты дорогих ресурсов, которые доступны в ограниченном количестве, например подключение к базе данных в пуле. [к оглавлению](#Многопоточность) -##Что такое _double checked locking Singleton_? +## Что такое _double checked locking Singleton_? __double checked locking Singleton__ - это один из способов создания потокобезопасного класса реализующего шаблон Одиночка. Данный метод пытается оптимизировать производительность, блокируясь только случае, когда экземпляр одиночки создаётся впервые. ```java @@ -680,7 +680,7 @@ class DoubleCheckedLockingSingleton { [к оглавлению](#Многопоточность) -##Как создать потокобезопасный Singleton? +## Как создать потокобезопасный Singleton? + __Static field__ ```java @@ -750,17 +750,17 @@ public class Singleton { [к оглавлению](#Многопоточность) -##Чем полезны неизменяемые объекты? +## Чем полезны неизменяемые объекты? Неизменяемость (immutability) помогает облегчить написание многопоточного кода. Неизменяемый объект может быть использован без какой-либо синхронизации. К сожалению в Java нет аннотации `@Immutable`, которая делает объект неизменяемым, для этого разработчикам нужно самим создавать класс с необходимыми характеристиками. Для этого необходимо следовать некоторым общим принципам: инициализация всех полей только конструкторе, отсутствие методов `setX()` вносящих изменения в поля класса, отсутствие утечек ссылки, организация отдельного хранилища копий изменяемых объектов и т.д. [к оглавлению](#Многопоточность) -##Что такое _busy spin_? +## Что такое _busy spin_? __busy spin__ – это техника, которую программисты используют, чтобы заставить поток ожидать при определённом условии. В отличие от традиционных методов `wait()`, `sleep()` или `yield()`, которые подразумевают уступку процессорного времени, этот метод вместо уступки выполняет пустой цикл. Это необходимо, для того, чтобы сохранить кэш процессора, т.к. в многоядерных системах, существует вероятность, что приостановленный поток продолжит своё выполнение уже на другом ядре, а это повлечет за собой перестройку состояния процессорного кэша, которая является достаточно затратной процедурой. [к оглавлению](#Многопоточность) -##Перечислите принципы, которым вы следуете в многопоточном программировании? +## Перечислите принципы, которым вы следуете в многопоточном программировании? При написании многопоточных программ следует придерживаться определённых правил, которые помогают обеспечить достойную производительность приложения в сочетании с удобной отладкой и простотой дальнейшей поддержки кода. + Всегда давайте значимые имена своим потокам. Процесс отладки, нахождения ошибок или отслеживание исключения в многопоточном коде – довольно сложная задача. `OrderProcessor`, `QuoteProcessor` или `TradeProcessor` намного информативнее, чем `Thread1`, `Thread2` и `Thread3`. Имя должно отражать задачу, выполняемую данным потоком. @@ -772,7 +772,7 @@ __busy spin__ – это техника, которую программисты [к оглавлению](#Многопоточность) -##Какое из следующих утверждений о потоках неверно? +## Какое из следующих утверждений о потоках неверно? 1. Если метод `start()` вызывается дважды для одного и того же объекта `Thread`, во время выполнения генерируется исключение. 2. Порядок, в котором запускались потоки, может не совпадать с порядком их фактического выполнения. 3. Если метод `run()` вызывается напрямую для объекта `Thread`, во время выполнения генерируется исключение. @@ -784,12 +784,12 @@ __busy spin__ – это техника, которую программисты [к оглавлению](#Многопоточность) -##Даны 3 потока Т1, Т2 и Т3? Как реализовать выполнение в последовательности Т1, Т2, Т3? +## Даны 3 потока Т1, Т2 и Т3? Как реализовать выполнение в последовательности Т1, Т2, Т3? Такой последовательности выполнения можно достичь многими способами, например просто воспользоваться методом `join()`, чтобы запустить поток в момент, когда другой уже закончит своё выполнение. Для реализации заданной последовательности, нужно запустить последний поток первым, и затем вызывать метод `join()` в обратном порядке, то есть Т3 вызывает `Т2.join`, а Т2 вызывает `Т1.join`, таким образом Т1 закончит выполнение первым, а Т3 последним. [к оглавлению](#Многопоточность) -##Напишите минимальный неблокирующий стек (всего два метода — `push()` и `pop()`). +## Напишите минимальный неблокирующий стек (всего два метода — `push()` и `pop()`). ```java class NonBlockingStack { private final AtomicReference head = new AtomicReference<>(null); @@ -827,7 +827,7 @@ class NonBlockingStack { [к оглавлению](#Многопоточность) -##Напишите минимальный неблокирующий стек (всего два метода — `push()` и `pop()`) с использованием `Semaphore`. +## Напишите минимальный неблокирующий стек (всего два метода — `push()` и `pop()`) с использованием `Semaphore`. ```java class SemaphoreStack { private final Semaphore semaphore = new Semaphore(1); @@ -872,7 +872,7 @@ class SemaphoreStack { [к оглавлению](#Многопоточность) -##Напишите минимальный неблокирующий ArrayList (всего четыре метода — `add()`, `get()`, `remove()`, `size()`). +## Напишите минимальный неблокирующий ArrayList (всего четыре метода — `add()`, `get()`, `remove()`, `size()`). ```java class NonBlockingArrayList { private volatile Object[] content = new Object[0]; @@ -927,7 +927,7 @@ class NonBlockingArrayList { [к оглавлению](#Многопоточность) -##Напишите потокобезопасную реализацию класса с неблокирующим методом `BigInteger next()`, который возвращает элементы последовательности: `[1, 2, 4, 8, 16, ...]`. +## Напишите потокобезопасную реализацию класса с неблокирующим методом `BigInteger next()`, который возвращает элементы последовательности: `[1, 2, 4, 8, 16, ...]`. ```java class PowerOfTwo { private AtomicReference current = new AtomicReference<>(null); @@ -944,7 +944,7 @@ class PowerOfTwo { ``` [к оглавлению](#Многопоточность) -##Напишите простейший многопоточный ограниченный буфер с использованием `synchronized`. +## Напишите простейший многопоточный ограниченный буфер с использованием `synchronized`. ```java class QueueSynchronized { private volatile int size = 0; @@ -1023,7 +1023,7 @@ class QueueSynchronized { ``` [к оглавлению](#Многопоточность) -##Напишите простейший многопоточный ограниченный буфер с использованием `ReentrantLock`. +## Напишите простейший многопоточный ограниченный буфер с использованием `ReentrantLock`. ```java class QueueReentrantLock {