GrabDuck

Собеседование по Java — многопоточность (вопросы и ответы)

:

Вопросы и ответы для собеседования Java по теме — многопоточность.

Вопросы

1. Дайте определение понятию “процесс”.
2. Дайте определение понятию “поток”.
3. Дайте определение понятию “синхронизация потоков”.
4. Как взаимодействуют программы, процессы и потоки?
5. В каких случаях целесообразно создавать несколько потоков?
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
7. Что вы знаете о главном потоке программы?
8. Какие есть способы создания и запуска потоков?
9. Какой метод запускает поток на выполнение?
10. Какой метод описывает действие потока во время выполнения?
11. Когда поток завершает свое выполнение?
12. Как синхронизировать метод?
13. Как принудительно остановить поток?
14. Дайте определение понятию “поток-демон”.
15. Как создать поток-демон?
16. Как получить текущий поток?
17. Дайте определение понятию “монитор”.
18. Как приостановить выполнение потока?
19. В каких состояниях может пребывать поток?
20. Что является монитором при вызове нестатического и статического метода?
21. Что является монитором при выполнении участка кода метода?
22. Какие методы позволяют синхронизировать выполнение потоков?
23. Какой метод переводит поток в режим ожидания?
24. Какова функциональность методов notify и notifyAll?
25. Что позволяет сделать метод join?
26. Каковы условия вызова метода wait/notify?
27. Дайте определение понятию “взаимная блокировка”.
28. Чем отличаются методы interrupt, interrupted, isInterrupted?
29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?
30. Модификаторы volatile и метод yield().
31. Пакет java.util.concurrent
32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?
33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?
34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?
35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?
36.Что такое ThreadGroup и зачем он нужен?
37.Что такое ThreadPool и зачем он нужен?
38.Что такое ThreadPoolExecutor и зачем он нужен?
39.Что такое «атомарные типы» в Java?
40.Зачем нужен класс ThreadLocal?
41.Что такое Executor?
42.Что такое ExecutorService?
43.Зачем нужен ScheduledExecutorService?

Ответы

1. Дайте определение понятию “процесс”.

Процесс — это совокупность кода и данных, разделяющих общее виртуальное адресное пространство. Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен (взаимодействие между процессами осуществляется с помощью специальных средств). Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.

Многопоточность в Java: http://habrahabr.ru/post/164487/

2. Дайте определение понятию “поток”.

Один поток («нить» или «трэд») – это одна единица исполнения кода. Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса.

Thinking in Java.Параллельное выполнение. http://wikijava.it-cache.net/index.php@title=Glava_17_Thinking_in_Java_4th_edition.html

3. Дайте определение понятию “синхронизация потоков”.

Синхронизация относится к многопоточности. Синхронизированный блок кода может быть выполнен только одним потоком одновременно.

Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация — это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванных непоследовательным доступом к общей памяти.
Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор.
Синхронизация достигается в Java использованием зарезервированного слова synchronized. Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized в переменных или атрибутах в определении класса.

Синхронизация потоков, блокировка объекта и блокировка класса info.javarush.ru: http://goo.gl/gW4ONp

4. Как взаимодействуют программы, процессы и потоки?

Чаще всего одна программа состоит из одного процесса, но бывают и исключения (например, браузер Chrome создает отдельный процесс для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга). В каждом процессе может быть создано множество потоков. Процессы разделены между собой (>программы), потоки в одном процессе могут взаимодействовать друг с другом (методы wait, notify, join и т.д.).

5. В каких случаях целесообразно создавать несколько потоков?

Многопоточные приложения применяются в случаях, когда можно разделить программу на несколько относительно независимых частей. В этом случае чтобы один код не ждал другой их помещают в различные потоки. В качестве примера можно привести программу с графическим интерфейсом — пока выполняются какие-либо длительные вычисления в одном потоке, интерфейс может быть доступен пользователю и не зависать, если он выполняется в другом потоке.

6. Что может произойти если два потока будут выполнять один и тот же код в программе?

Если используются не синхронизированные данные, то может произойти ситуация, когда код работает уже с устаревшими данными. Например, в первом потоке идет изменение каких-либо полей, а в это время второй поток читает эти поля.

7. Что вы знаете о главном потоке программы?

Маленькие программы на Java обычно состоят из одной нити, называемой «главной нитью» (main thread). Но программы побольше часто запускают дополнительные нити, их еще называют «дочерними нитями». Главная нить выполняет метод main и завершается. Аналогом такого метода main, для дочерних нитей служит метод run интерфейса Runnable. Много потоков — много методов main (run()).

8. Какие есть способы создания и запуска потоков?

Существует несколько способов создания и запуска потоков.

С помощью класса, реализующего Runnable

  • Создать объект класса Thread.
  • Создать объект класса, реализующего интерфейс Runnable
  • Вызвать у созданного объекта Thread метод start() (после этого запустится метод run() у переданного объекта, реализующего Runnable)

С помощью класса, расширяющего Thread

  • Создать объект класса ClassName extends Thread.
  • Переопределить run() в этом классе (смотрите примере ниже, где передается имя потока ‘Second’)

С помощью класса, реализующего java.util.concurrent.Callable

  • Создать объект класса, реализующего интерфейс Callable
  • Создать объект ExecutorService с указанием пула потоков.
  • Создать объект Future. Запуск происходит через метод submit(); Сигнатура: <T> Future<T> submit(Callable<T> task)

Пример с интерфейсом Callable:

9. Какой метод запускает поток на выполнение?

Thread.start() запускает дочерний поток. Для интерфейса Callable запуск потока осуществляется с помощью метода submit().

10. Какой метод описывает действие потока во время выполнения?

Метод run() или метод call() для дочерних потоков. Метод main() для главного потока.

11. Когда поток завершает свое выполнение?

Поток закончит выполнение, когда завершится его метод run() или call(). Для главного потока это метод main().

12. Как синхронизировать метод?

Или указать в сигнатуре модификатор synchronized или использовать конструкцию synchronized {} внутри метода.

13. Как принудительно остановить поток?

В Java 8 нет метода, который бы принудительно останавливал поток. Никто не гарантирует, что нить можно остановить. Она может остановиться только сама. Java имеет встроенный механизм оповещения потока, который называется Interruption (прерывание, вмешательство).

Класс Thread содержит в себе скрытое булево поле, которое называется флагом прерывания. Установить этот флаг можно вызвав метод interrupt() потока. Проверить же, установлен ли этот флаг, можно двумя способами. Первый способ — вызвать метод bool isInterrupted() объекта потока, второй — вызвать статический метод bool Thread.interrupted(). Первый метод возвращает состояние флага прерывания и оставляет этот флаг нетронутым. Второй метод возвращает состояние флага и сбрасывает его. Заметьте что Thread.interrupted() — статический метод класса Thread, и его вызов возвращает значение флага прерывания того потока, из которого он был вызван. Поэтому этот метод вызывается только изнутри потока и позволяет потоку проверить своё состояние прерывания.

У методов, приостанавливающих выполнение потока, таких как sleep(), wait() и join() есть одна особенность — если во время их выполнения будет вызван метод interrupt() этого потока, они, не дожидаясь конца времени ожидания, сгенерируют исключение InterruptedException.

  • В методе main() создаем объект класса JoinClass и запускаем его методом run(). Сначала проверяется не завершен ли уже этот поток, а затем каждые 100мс выводится значение счетчика.
  • Главному методу приказывает подождать 1000мс, чтобы счетчик успел немножко посчитать.
  • Вызываем interrupt метод у объекта класса JoinClass. После этого в цикле сразу ловится исключение и срабатывает return в блоке catch.

14. Дайте определение понятию “поток-демон”.

Потоками-демонами называются потоки, работающие в фоновом режиме для нашей программы.

В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main() уже завершился, но еще выполняются порожденные им потоки, система будет ждать их завершения.
Однако это правило не относится к особому виду потоков – демонам. Если завершился последний обычный поток процесса, и остались только потоки-демоны, то они будут принудительно завершены и выполнение процесса закончится. Чаще всего потоки-демоны используются для выполнения фоновых задач, обслуживающих процесс в течение его жизни.

15. Как создать поток-демон?

Объявить поток демоном достаточно просто — нужно перед запуском потока вызвать его метод setDaemon(true);
Проверить, является ли поток демоном, можно вызвав его метод boolean isDaemon();

16. Как получить текущий поток?

Вызвать в коде статический метод Thread.currentThread(), который вернет текущий поток.

17. Дайте определение понятию “монитор”.

Несколько нитей могут мешать друг другу при обращении к одним и тем же данным. Для решения этой проблемы придуман мьютекс (он же монитор). Он имеет два состояния — объект занят и объект свободен. Монитор(мьютекс) — высокоуровневый механизм взаимодействия и синхронизации процессов, обеспечивающий доступ к неразделяемым ресурсам.

Когда какой-то нити нужен общий для всех нитей объект, она проверяет мютекс, связанный с этим объектом. Если мютекс свободен, то нить блокирует его (помечает как занятый) и начинает использование общего ресурса. После того, как она сделала свои дела, мютекс разблокируется (помечается как свободен).

Если же нить хочет использовать объект, а мютекс заблокирован, то нить засыпает в ожидании. Когда мютекс, наконец, освободится занятой нитью, наша нить тут же заблокирует его и приступит к работе. Мютекс встроен в класс Object и следовательно он есть у каждого объекта.

Когда одна нить заходит внутрь блока кода, помеченного словом synchronized, то Java-машина тут же блокирует мютекс у объекта, который указан в круглых скобках после слова synchronized. Больше ни одна нить не сможет зайти в этот блок, пока наша нить его не покинет. Как только наша нить выйдет из блока, помеченного synchronized, то мютекс тут же автоматически разблокируется и будет свободен для захвата другой нитью. Если же мютекс был занят, то наша нить будет стоять на месте и ждать когда он освободится.

18. Как приостановить выполнение потока?

Thread.sleep() — статический метод класса Thread, который приостанавливает выполнение потока, в котором он был вызван. Во время выполнения метода sleep() система перестает выделять потоку процессорное время, распределяя его между другими потоками. Метод sleep() может выполняться либо заданное кол-во времени (миллисекунды или наносекунды) либо до тех пор пока он не будет остановлен прерыванием (в этом случае он сгенерирует исключение InterruptedException).

Возможен вопрос про старые методы suspend, stop и resume — они deprecated.

19. В каких состояниях может пребывать поток?

Поток может быть в следующем состоянии: созданный, запущенный, блокированный, остановленный, в режиме ожидания, в режиме ожидания по времени (NEW, RUNNABLE, BLOCKED, TERMINATED, WAITING, TIMED_WAITING).

20. Что является монитором при вызове нестатического и статического метода?

Для нестатического метода — текущий объект this. Для статического метода — объекта типа Class, соответствующий классу, в котором определен этот метод.

21. Что является монитором при выполнении участка кода метода?

Монитором является объект, указанный в блоке synchronized участка кода:

22. Какие методы позволяют синхронизировать выполнение потоков?

К этим методам относятся notify(), notifyAll(), wait();

А как же всё-таки работает многопоточность? Часть I: синхронизация: http://habrahabr.ru/post/143237/

23. Какой метод переводит поток в режим ожидания?

Метод wait().

24. Какова функциональность методов notify и notifyAll?

Метод notify пробуждает один из потоков, который вызвал метод wait() у этого монитора. Метод notifyAll пробуждает все потоки. Очередность выполнения в этом случае будет определяться приоритетом потока.

25. Что позволяет сделать метод join?

Одна нить (поток) может вызвать метод join() у другой нити. В результате первый поток (который вызвал метод) приостанавливает свою работу и ждет окончания работы второго потока (у объекта которого был вызван метод join()).

После запуска метода main создается главный поток класса TestClass.

  • Затем мы создаем тестовый поток threadExample и запускаем его.Заставляем поток протупить 5 секунд внутри метода JoinClass.run().
  • После чего вызываем метод join() у второго потока. В этот момент главный поток подсоединяется к нашему второму потоку и ждет его завершения.
  • Смотрим какое прошло время — 5 секунд. Т.е. главный поток ждал пока завершится threadExample до перехода к методу System.out.println(). В противном случае System.out.println(«END: «) выполнился сразу без ожидания пока отойдет threadExample.

26. Каковы условия вызова метода wait/notify?

Методы должны вызываться на объекте-мониторе только из синхронизированного кода. Поток, который вызывает эти методы должен владеть монитором, иначе будет выдано исключение java.lang.IllegalMonitorStateException.

27. Дайте определение понятию “взаимная блокировка”.

Deadlock, он же взаимная блокировка, явление при котором все потоки находятся в режиме ожидания. Чтобы уменьшить шанс появления deadlock’a не рекомендуется использовать методы wait() и notify().

Чтобы избежать дедлока можно использовать только один блок synchronized, отказаться от wait-notify или использовать такую конструкцию:

Java — Thread Deadlock: http://www.tutorialspoint.com/java/java_thread_deadlock.htm
Взаимная блокировка(deadlock) в Java и методы борьбы с ней: http://www.developersonthe.net/ru/posts/post_id/34-Vzaimnaja-blokirovkadeadlock-v-Java-i-metody-borby-s-nej/

28. Чем отличаются методы interrupt, interrupted, isInterrupted?

  • Метод interrupt() — устанавливает флаг прерывания потока.
  • Метод bool isInterrupted() объекта потока возвращает состояние флага прерывания и оставляет этот флаг нетронутым.
  • Статический метод bool Thread.interrupted() — возвращает состояние флага и сбрасывает его.

29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?

Методы, требующие обработку этого исключения: wait, sleep, join. Исключение будет выброшено, если флаг interrupt у потока true.

30. Модификаторы volatile и метод yield().

Помещение модификатора volatile перед определением переменной заставляет принудительно всегда читать и писать значение только в обычную (медленную) память (а не кэшировать). Записывается как: private volatile boolean varName;

Статический метод Thread.yield() заставляет процессор переключиться на обработку других потоков системы. Метод может быть полезным, например, когда поток ожидает наступления какого-либо события и необходимо чтобы проверка его наступления происходила как можно чаще. В этом случае можно поместить проверку события и метод Thread.yield() в цикл:

31. Пакет java.util.concurrent

Обзор java.util.concurrent.*:  http://habrahabr.ru/company/luxoft/blog/157273/

32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?

Да, иначе будет состояние гонки (race condition).

33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?

Да, можно создать новый экземпляр класса, пока его static synchronized метод выполняется. Мои рассуждения: статик методы классов создаются в единственном экземпляре во время загрузки класса класслоадером и принадлежат объекту Class.MyClass. Во время выполнения в потоке static synchronized метода захватывается блокировка именно этого объекта. Следовательно нам ни что не мешает создать новый экземпляр класса. Загрузчиков может быть несколько, соответственно, экземпляров Class<MyClass> — тоже несколько, по одному на загрузчик. Создавать новые инстанции ничего не мешает, т.к. это типа выделение машиной памяти под объект и вызов метода <init>, который не synchronized.

34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?

Если в дочернем потоке упадет Exception, то метод run() аварийно завершится и исключение будет передано в главный поток. Далее в консоль будет выведен стектрейс, приведенный ниже.

Если исключение не обрабатывать, то нить (вызванная в методе run()) просто аварийно завершится. Восстановить работу нити после такого сценария нельзя, можно только создать нить заново.

35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?

Читайте выше о пакете concurrent. Реализация стандартными пакетами в статье от ibm:

Теория и практика Java: Пулы потоков и очередь действий: http://www.ibm.com/developerworks/ru/library/j-jtp0730/index.html

36.Что такое ThreadGroup и зачем он нужен?

ThreadGroup представляет собой набор нитей, которые так же могут содержать в себе другие группы потоков. Группа нитей образует дерево, в котором каждая другая группа нитей имеет родителя (кроме исходной). Поток имеет право доступа к данным из своей группы нитей, но не имеет такого доступа к другим группам или к родительской группе потоков.

Управление потоками, безопасность и ThreadGroup Java — http://src-code.net/upravlenie-potokami-bezopasnost-i-threadgroup-java/

37.Что такое ThreadPool и зачем он нужен?

Пулы потоков (нитей) представляют собой управляемую коллекцию потоков, которые доступны для выполнения различных задач. Пулы нитей, как правило, обеспечивают:

  • Повышение производительности при выполнении большого количества задач в связи с сокращением накладных расходов на вызов каждой задачи.
  • Является средством ограничивающим расход ресурсов при выполнении набора задач.
  • Избавляют от необходимости управления жизненным циклом нитей.

38.Что такое ThreadPoolExecutor и зачем он нужен?

ThreadPoolExecutor — реализация ExecutorService. Он выполняет переданную задачу (Callable или Runnable), используя одну из внутренних доступных нитей из пула. Пул потоков содержит в себе ThreadPoolExecutor, который может содержать изменяющееся число нитей. Число нитей в пуле задается с помощью corePoolSize и maximumPoolSize.

http://tutorials.jenkov.com/java-util-concurrent/threadpoolexecutor.html

39.Что такое «атомарные типы» в Java?

Все атомарные классы переменных имеют базовый элемент Сравнение и назначение (compare-and-set) (аналогичный элементу Сравнение и замена), который реализуется при помощи самого быстрого собственного структурного компонента, кооторый имеется в платформе (Сравнение и замена, Загрузить в связке, Сохранить при условии или в крайнем случае спин-блокировками). В пакет java.util.concurrent.atomic входят 9 видов атомарных переменных (AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean; формы для массивов атомарных целых чисел; длинные (long); ссылки; а также атомарные с пометкой Класс эталона (reference), которые атомарно обновляют две величины).
Классы атомарных переменных можно рассматривать как обобщение volatile переменных, если расширить понятие изменяемых переменных до переменных с поддержкой атомарных обновлений методом Сравнение и назначение. Чтение и запись атомарных переменных имеет такую же семантику памяти как доступ к чтению и записи изменяемых переменных.

Пример атомарной переменной AtomicLong:

Переменные current и next — локальные, а следовательно у каждого потока свои экземпляры этих переменных. Следует обратить внимание лишь на разделяемое состояние, т.е. переменную value. Т.к. переменная value объявлена с модификатором volatile, то гарантируется выполнение отношения happens-before, что ведет к тому, что измененное значение этой переменной увидят все потоки.

Метод compareAndSet представляет из себя механизм оптимистичной блокировки и позволяет изменить значение value, только если оно равно ожидаемому значению (т.е. current).

Если же значение value было изменено в другом потоке, то оно не будет равно ожидаемому значению. Следовательно метод compareAndSet вернет значение false, что приведет к новой итерации цикла while в методе getAndAdd. Т.е. новое значение value будет перезачитано в переменную current, после чего будет произведено сложение и новая попытка записи получившегося значения (т.е. next).

http://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/

40.Зачем нужен класс ThreadLocal?

ThreadLocal предоставляет абстракцию над переменными локальными по отношению к потоку исполнения java.lang.Thread. ThreadLocal переменные отличаются от обычных переменных тем, что у каждого потока свой собственный, индивидуально инициализируемый экземпляр переменной, доступ к которой он получает через методы get() или set(). У каждого потока — т.е. экземпляра класса Thread — есть ассоциированная с ним таблица ThreadLocal-переменных. Ключами таблицы являются cсылки на объекты класса ThreadLocal, а значениями — ссылки на объекты, «захваченные» ThreadLocal-переменными.

http://samolisov.blogspot.ru/2011/04/threadlocal.html
http://articles.javatalks.ru/articles/17

41.Что такое Executor?

Executor — интерфейс, который может выполнять подтвержденные задачи. Интерфейс предоставляет возможность избежать вникания в механику выполнения задачи и деталей использования выполняемого потока. Executor обычно используется для явного создания нитей. Например так:

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html

42.Что такое ExecutorService?

ExecutorService исполняет асинхронный код в одном или нескольких потоках. Создание инстанса ExecutorService’а делается либо вручную через конкретные имплементации (ScheduledThreadPoolExecutor или ThreadPoolExecutor), но проще будет использовать фабрики класса Executors. Например, если надо создать пул с 2мя потоками, то делается это так:

Если требуется использовать кэширующий пул потоков, который создает потоки по мере необходимости, но переиспользует неактивные потоки (и подчищает потоки, которые были неактивные некоторое время), то это задается следующим образом:

Если требуется запустить асинхронный код несколько раз, то это будет выполняться так:

Метод submit также возвращает объект Future, который содержит информацию о статусе исполнения переданного Runnable или Callable (который может возвращать значение). Из него можно узнать выполнился ли переданный код успешно, или он еще выполняется. Вызов метода get на объекте Future возвратит значение, который возвращает Callable (или null, если используется Runnable). Метод имеет 2 checked-исключения: InterruptedException, который бросается, когда выполнение прервано через метод interrupt(), или ExecutionException если код в Runnable или Callable бросил RuntimeException, что решает проблему поддержки исключений между потоками.

Многопоточность в Java: ExecutorService — https://habrahabr.ru/post/116363/

43.Зачем нужен ScheduledExecutorService?

Иногда требуется выполнение кода асихронно и периодически или требуется выполнить код через некоторое время, тогда на помощь приходит ScheduledExecutorService. Он позволяет поставить код выполняться в одном или нескольких потоках и сконфигурировать интервал или время, на которое выполненение будет отложено. Интервалом может быть время между двумя последовательными запусками или время между окончанием одного выполнения и началом другого. Методы ScheduledExecutorService возвращают ScheduledFuture, который также содержит значение отсрочки для выполнения ScheduledFuture.

Если требуется отложить выполнение на 5 секунд, потребуется следующий код:

Если требуется назначить выполнение каждую секунду:

И, наконец, если требуется назначить выполнение кода с промежутком 1 секунда между выполнениями:

Многопоточность в Java: ExecutorService — https://habrahabr.ru/post/116363/
39

8412 Total Views 1 Views Today