JavaRush /Java блог /Архив info.javarush /Перевод: Топ-50 интервью вопросов по нитям. Часть 1.
KapChook
19 уровень
Volga

Перевод: Топ-50 интервью вопросов по нитям. Часть 1.

Статья из группы Архив info.javarush
Первая часть перевода оригинальной статьи Top 50 Java Thread Interview Questions Answers for Freshers, Experienced Programmers. Вторая часть. Примечание: статья оказалось большой, от чего не помещается в один топик. К тому же довольно сложной, как мог старался гуглить, но всё же. Поэтому просьба к участникам, дружащим с английским, одним глазком проглядеть оригинал и сравнить с переводом, вдруг что не так понял и неправильно перевёл. Заранее спасибо. На любом интервью, для сеньоров или джуниоров, для опытных или начинающих, вы столкнётесь с парой вопросов о нитях, параллелизме и многонитиевости. Фактически эта встроенная поддержка параллелизма является одним из сильнейших достоинств Java и помогла ей достичь популярности равно среди предпренимателей и программистов. Большая часть прибыльных позиций Java разработчика требуют превосходного навыка многонитиевости и опыта разработки, отладки и настройки высокопроизводительных приложений с низкой задержкой. Поэтому это один из самых востребованых навыков на интервью. В типичном Java интервью интервьюер медленно начинает с базовых концептов нити, задавая вопросы, такие как зачем нужны нити, как их создать, какой способ создания лучше, наследованием от Thread или реализацией Runnable, и затем медленно переходит к трудностям параллелизма, трудностям, встречающимся при разработке параллельных приложений, высокоуровневым утилитам параллелизма, представленным в JDK 1.5, принципам и дизайн паттернам параллельных приложений и классическим проблемам многонитиевости. Так как не достаточно просто знать азы многонитиевости, вы должны знать, как справляться с проблемами параллелизма, такими как взаимная блокировка, состояние гонки, несогласованность памяти и различные проблемы ните-безопасности. Эти навыки тщательно тестируются, представляя различные проблемы многонитиевости и параллелизма. Многие Java разработчики обычно просто прочитывают вопросы, перед самим интервью, что не плохо, но вам стоит разбираться в этом. Также накопление вопросов и выполнение одинаковых упражнений тратит очень много времени, поэтому я создал этот список.
  1. Что такое нить в Java?

  2. Нить – это независимый путь выполнения. Её цель – воспользоваться преимуществом нескольких процессоров, доступных в машине. Используя несколько нитей, вы можете ускорить задачи, привязанные к процессору. Например, если одной нити нужно 100 миллисекунд на выполнение работы, вы можете использовать 10 нитей, чтобы сократить эту работу до 10 миллисекунд. Java предоставляет отличную поддержку многонитиевости на уровне языка и это, к тому же, одно из сильнейших её достоинств.
  3. Различие между нитями и процессами в Java?

  4. Нить – это подмножество процесса, другими словами один процесс может содержать множество нитей. Два процесса исполняются на различных пространствах памяти, но все нити делят одно пространство. Не спутайте это с памятью стека, которая различная для каждой нити и используется для хранения локальных данных этой нити.
  5. Как создать нить?

  6. На уровне языка есть два способа создания нити. Объект класса java.lang.Thread представляет собой нить, но ей требуется задача для исполнения, которая является объектом, реализующим интерфейс java.lang.Runnable. Так как класс Thread реализует интерфейс Runnable, вы можете переопределить метод run() унаследовав ваш класс от Thread или реализовав в нём интерфейс Runnable.
  7. Когда использовать Runnable и когда Thread?

  8. Это дополнение к предудыщему вопросу. Как мы знаем, нить можно создать унаследовавшись от класса Thread или реализовав интерфейс Runnable. Возникает вопрос, который из способов лучше и когда какой использовать? На этот вопрос легко ответь, если вы знаете, что Java не поддерживает множественное наследование классов, но позволяет реализовывать множество интерфейсов. Что означает, что лучше реализовывать Runnable, если вы хотите унаследоваться от другого класса.
  9. Разница между методами start() и run()?

  10. Один из вопросов с подвохом из прошлого, но он всё ещё достаточно хорош, чтобы отличить поверхностное понимание многонитиевости в Java. Метод start() используется для запуска новой нити. Несмотря на то, что start() вызывает метод run() внутри себя, это не то же самое, что просто вызвать run(). Если вы вызываете run() как обычный метод, он вызывается в той же нити и никакая новая нить не запуститься, что происходит, когда вы вызываете метод start().
  11. Различия Runnable и Callable?

  12. Оба интерфейса представляют задачи, которые предназначены для выполнения в отдельных нитях. Runnable существует ещё с JDK 1.0, а Callable был добавлен в JDK 1.5. Главное их различие заключается в том, что метод call() у Callable может возвращать значения и выбрасывать исключения, что невозможно в методе run() у Runnable. Callable возвращает объект Future, который может содержать результат вычислений.
  13. Различия между CyclicBarrier и CountDownLatch?

  14. Хоть оба эти синхронизаторы позволяют нитям дожидаться друг друга, главное различие между ними в том, что вы не можете заново использовать CountDownLatch после того, как его счётчик достигнет нуля, но вы можете использовать CyclicBarrier снова, даже после того, как барьер сломается.
  15. Что такое модель памяти Java?

  16. Модель памяти – это набор правил и указаний, которые позволяют Java программам действовать детерминировано среди множества архитектур памяти, процессора и операционной системы. Это особенно важно в случае многонитиевости. Модель памяти предоставляет гарантии того, что изменения, произведённые одной нитью, будут видны для других, одна из них – отношение happens-before (случалось ранее). Это отношение определяет несколько правил, которые позволяют программистам предвидеть и определять поведение параллельных программ. Например happens-before гарантирует:
    • Каждое действие в нити случается раньше любого действия в этой нити, которое следует в программном порядке, это также известно, как правило программного порядка.
    • Разблокировка монитора случается раньше каждой последующей блокировки того же самого монитора, также известно, как правило блокировки Монитора.
    • Запись volatile поля случается раньше каждого последующего чтения этого поля, правило изменчивой переменной.
    • Вызов Thread.start() в нити случается раньше, чем любая другая нить замечает, что нить была остановлена, либо после удачного Thread.join(), либо если Thread.isAlive() возвращает false, правило Thread.start().
    • Прерывание нити из другой нити случается раньше, чем прерванная нить заметит прерывание (либо от выброса InterruptedException, либо от проверки isInterrupted()), правило прерывания нити.
    • Окончание конструктора объекта случается раньше, чем запуск финализатора для этого объекта, правило Финализатора.
    • Если А случается раньше В, и В случается раньше С, значит А случается раньше С, что значит happens-before гарантирует транзитивность.
  17. Что такое volatile переменная?

  18. Volatile – специальный модификатор, который может применяться только к атрибутам. В параллельных Java программах изменения, произведённые различными нитями на атрибутах, не видны для остальных при отсутствии синхронизатора. Volatile переменная гарантирует, что запись будет осуществляться до последующего чтения, что сказано в правиле изменчивой переменной в предыдущем вопросе.
  19. Что такое ните-безопасность? Безопасный ли класс Vector?

  20. Ните-безопасность – свойвство объекта или кода, которое гарантирует, что при исполнении или использовании несколькими нитями, код будет вести себя, как предполагается. Например ните-безопасный счётчик не пропустит ни один счёт, если тот же экземпляр счётчика используется среди нескольких нитей. Очевидно, можно разделить классы коллекций на две категории, ните-безопасные и не-ните-безопасные. Vector ните-безопасен и достигает этого синхронизацией методов, которые изменяют состояние Vector’a, с другой стороны его коллега ArrayList не-ните-безопасен.
  21. Что такое состояние гонки (race condition)?

  22. Состояние гонки – причина трудноуловимых багов. Как сказано в самом названии, состояние гонки возникает из-за гонки между несколькими нитями, если нить, которая должна исполнятся первой, проиграла гонку и исполняется вторая, поведение кода изменяется, из-за чего возникают недетерменированные баги. Это одни из сложнейших к отлавливанию и воспроизведению багов, из-за беспорядочной природы гонок между нитями. Пример состояния гонки – беспорядочное исполнение.
  23. Как остановить нить?

  24. Я всегда говорил, что Java предоставляет богатые API для всего, но, по иронии судьбы, не предоставляет удобных способов остановки нити. В JDK 1.0 было несколько управляющих методов, например stop(), suspend() и resume(), которые были помечены как deprecated в будущих релизах из-за потенциальных угроз взаимной блокировки, с тех пор разработчики Java API не предприняли попыток представить стойкий, ните-безопасный и элегантный способ остановки нитей. Программисты в основном полагаются на факт того, что нить останавливается сама, как только заканчивает выполнять методы run() или call(). Для остановки вручную, программисты пользуются преимуществом volatile boolean переменной и проверяют её значение в каждой итерации, если в методе run() есть циклы, или прерывают нити методом interrupt() для внезапной отмены заданий.
  25. Что происходит, когда в нити появляется исключение?

  26. Это один из хороших вопросов с подвохом. Простыми словами, если исключение не поймано – нить мерта, если установлен обработчик непойманных исключений, он получит колбек. Thread.UncaughtExceptionHandler – интерфейс, определённый как вложенный интерфейс для обработчиков, вызываемых, когда нить внезапно останавливается из-за непойманного исключения. Когда нить собирается остановится из-за непойманного исключения, JVM проверит её на наличие UncaughtExceptionHandler, используя Thread.getUncaughtExceptionHandler(), и вызовет у обработчика метод uncaughtException(), передав нить и исключение в виде аргументов.
  27. Как поделиться данными между двумя нитями?

  28. Вы можете делиться данным между нитями, используя общий объект или параллельные структуры данных, типа BlockingQueue.
  29. Различия между notify и notifyAll?

  30. Это ещё один из вопросов с подвохом, так как за одним монитором могут наблюдать несколько нитей, Java API разработчики предоставляют метод для уведомления об изменении его состояния только одной или сразу всех нитей, но они предоставляют только половину реализации. У метода notify() не реализован способ выбора определённой нити, поэтому он полезен только когда вы точно знаете, что всего одна нить ожидает. С другой стороны, notifyAll() уведомляет все нити и позволяет им побороться за монитор, что гарантирует, что по крайней мере одна нить продвинется дальше.
  31. Почему wait, notify и notifyAll не в классе Thread?

  32. Это вопрос, относящийся к дизайну, который проверяет, что кандидат думает о существующих системах или думал ли он когда-либо о чём-то схожем, но выглядящем неуместно поначалу. Чтобы ответить на этот вопрос, вам нужно предоставить несколько причин, почему эти методы удобнее реализовывать в классе Object, и почему не в классе Thread. Первая очевидная причина – Java поддерживает lock на уровне объектов, а не на уровне нитей. Любой объект имеет lock, который получает нить. И если нити нужно ждать определённый lock, есть смысл в том, чтобы вызвать wait() на объект, чем на эту нить. Если бы wait() был объявлен в классе Thread, было бы не ясно, какой lock нить ждёт. Вкратце, так как wait, notify и notifyAll работают на уровне lock, удобнее объявить их в классе Object, потому что lock относится к объекту.
  33. Что такое ThreadLocal переменная?

  34. ThreadLocal переменные – специальный вид переменных, доступных Java программисту. Так же, как для состояний есть переменная состояния, для нитей есть ThreadLocal переменные. Это неплохой способ достичь ните-безопасности для затратных-для-создания объектов, например вы можете сделать SimpleDateFormat ните-безопасным, используя ThreadLocal. Так как это затратный класс, его нежелательно использовать в локальной области, которая требует отдельных экземпляров на каждый вызов. Предоставляя каждой нити её собственную копию, вы убиваете двух зайцев. Во-первых, вы уменьшаете количество экземпляров затратных объектов, используя по новой фиксированное количество экземпляров, и во-вторых, вы достигаете ните-безопасности, без потерь синхронизации и неизменяемости. Ещё один хороший пример локальной переменной у нити – класс ThreadLocalRandom, который уменьшает количество экземпляров затратных-для-создания объектов Random в много-нитиевой среде.
  35. Что такое FutureTask?

  36. FutureTask представляет собой отменяемое асинхронное вычисление в параллельном Java приложении. Этот класс предоставляет базовую реализацию Future, с методами для запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения результатов. Результат может быть получен только когда вычисление завершено, метод получения будет заблокирован, если вычисление ещё не завершено. Объекты FutureTask могут быть использованы для обёртки объектов Callable и Runnable. Так как FutureTask реализует Runnable, его можно передать Executor’у на выполнение.
  37. Различие между interrupted и isInterrupted?

  38. Основное различие между interrupted() и isInterrupted() в том, что первый сбрасывает статус прерывания, а второй нет. Механизм прерывания в Java реализован с использованием внутреннего флага, известного как статус прерывания. Прерывание нити вызовом Thread.interrupt() устанавливает этот флаг. Когда прерванная нить проверяет статус прерывания, вызывая статический метод Thread.interrupted(), статус прерывания сбрасывается. Нестатический метод isInterrupted(), который используется нитью для проверки статуса прерывания у другой нити, не изменяет флаг прерывания. Условно, любой метод, который завершается, выкинув InterruptedException сбрасывает при этом флаг прерывания. Однако, всегда существует возможность того, что флаг тут же снова установится, если другая нить вызовет interrupt().
  39. Почему методы wait и notify вызываются в синхронизированном блоке?

  40. Основная причина вызова wait и notify из статического блока или метода в том, что Java API обязательно требует этого. Если вы вызовете их не из синхронизированного блока, ваш код выбросит IllegalMonitorStateException. Более хитрая причина в том, чтобы избежать состояния гонки между вызовами wait и notify.
  41. Почему вы должны проверять состояние ожидания в цикле?

  42. Существует возможность того, что ожидающая нить получит ложные предупреждения и ложные вызовы пробуждения, если она не проверит состояние ожидания в цикле, она просто выйдет, даже если состояние не достигнуто. Когда ожидающая нить пробуждается, она не думает о том, что состояние, которое она ожидала, может все ещё оставаться в силе. Оно могло быть действительно в прошлом, но потом быть изменено после вызова метода notify() и перед тем как нить пробудилась. Поэтому всегда лучше вызывать wait() из цикла.
  43. Различия между synchronized и concurrent коллекциями?

  44. Хоть обе synchronized и concurrent коллекции предоставляют ните-безопасные коллекции, последняя является более масштабируемой. До Java 1.5 программистам были доступны только synchronized коллекции, которые становились источником раздора, когда несколько нитей обращались к ним одновременно, что затрудняло масштабирование системы. Java 5 представила concurrent коллекции, например ConcurrentHashMap, которые не только предоставляют ните-безопасность, но также улучшают масштабируемость, используя современные техники, такие как lock stripping и partitioning internal table.
  45. Различия между Стеком и Кучей?

  46. Почему этот вопрос присутствует в вопросах о много-нитиевости? Потому что стек – участок памяти, тесно связанный с нитями. У каждой нити есть свой стек, которые хранит локальные переменные, параметры методов и стек вызовов. Переменная, хранящаяся в стеке одной нити, не видна для другой. С другой стороны, куча – общий участок памяти, который делится между всеми нитями. Объекты, неважно локальные или любого другого уровня, создаются в куче. Для улучшения производительности, нить обычно кэширует значения из кучи в свой стек, тут-то выползают volatile переменные. Volatile указывает нитям на то, что переменную нужно читать из главной памяти.
  47. Что такое пул нитей?

  48. Создание нити затратно в плане времени и ресурсов. Если вы создаёте нить во время обработки запроса, это замедлит время отклика, также процесс может создать только ограниченное число нитей. Чтобы избежать этих проблем, во время запуска приложения создаётся пул нитей и нити повторно используются для обработки запросов. Этот пул нитей называется «thread pool», а нити в нём – рабочая нить. Начиная с Java 1.5 Java API предоставляет фреймворк Executor, который позволяет вам создавать различные пулы нитей, например single thread pool, который обрабатывает только одно задание за единицу времени, fixed thread pool, пул с фиксированным количеством нитей, и cached thread pool, расширяемый пул, подходящий для приложений с множеством недолгих заданий.
  49. Как решить Producer Consumer проблему?

  50. Большинство нитиевых проблем, которые вы решаете в реальности, из категории паттерна Producer Consumer, в котором одна нить порождает задачу, а вторая поглощает её. Вам нужно знать, как построить внутренние взаимодействия нитей, для решения этой проблемы. На низком уровне вы можете воспользоваться методами wait и notify, а на высоком уровне вы можете воспользоваться преимуществами Semaphore или BlockingQueue
  51. Как избежать взаимной блокировки (deadlock)?

  52. Перевод: Топ-50 интервью вопросов по нитям. Часть 1. - 1 Deadlock – состояние, в котором нить ждёт, пока вторая нить совершит какое-либо действие, а вторая, в это же время, ждёт того же от первой. Это очень серьёзная проблема, из-за которой ваша программа зависает и не делает того, для чего она предназначена. Deadlock происходит, когда достигаются эти 4 состояния:
    • Взаимное исключение: по крайней мере один ресурс должен быть занят в режиме неделимости. Только один процесс может использовать ресурс в любой данный момент времени.
    • Удержание и ожидание: процесс удерживает как минимум один ресурс и запрашивает дополнительных ресурсов, которые удерживаются другими процессами.
    • Нет пред-очистке: операционная система не переназначивает ресурсы, если они уже заняты, они должны отдаваться удерживающим процессом добровольно.
    • Цикличное ожидание: процесс ждёт освобождения ресурсов другим процессом, который в свою очередь ждёт освобождения ресурсов первым процессом.
    Простейший способ избежать взаимной блокировки – не допускать цикличного ожидания, этого можно достичь, получая lock'и в определённом порядке и освобождая их в обратном порядке.
  53. Различия между livelock и deadlock?

  54. Livelock схож с deadlock, только в livelock состояния нитей или вовлечённых процессов постоянно изменяются в зависимости друг от друга. Livelock – особый случай нехватки ресурсов. Реальный пример livelock'а – когда два человека встречаются в узком коридоре и каждый, пытаясь быть вежливым, отходит в сторону, и так они бесконечно двигаются из стороны в сторону.
Комментарии (11)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
hidden #2882717 Уровень 2
5 ноября 2021
Ава классная
22 сентября 2020
https://javarush.com/groups/posts/1129-perevod-top-50-intervjhju-voprosov-po-nitjam-chastjh-2 Вторая часть тут
Khanan Уровень 32
5 июля 2018
1.Разве Callable возвращает Future? В доках написано "V call()", а V описано как "V - the result type of method call". (6 вопрос, различия Callable и Runnable) 2. "или прерывают нити методом interrupt() для внезапной отмены заданий" , interrupt() не дает гарантий того, что нить остановится, метод просто меняет флаг interrupted у нити, прерывание нити остается на совести разработчиков (19 вопрос, Различие между interrupted и isInterrupted)
SergioShapoval Уровень 41
1 июля 2015
Volatile – специальный модификатор, который может применяться только к атрибутам

KapChook может лучше полям, а не атрибутам?
Eva_Sky Уровень 30
7 марта 2015
Как раз на 16 уровне начала изучать нити. Очень полезно, спасибо!
bulldorez Уровень 23
26 февраля 2015
Отличная статья!) Спасибо
Olegator3 Уровень 37
21 февраля 2015
прочел, обалденная, личная просьба, можешь скинуть материла по CountDownLatch если такой имеется, так и не понял я его работу, да и в курсе всего 1 задача была на него: с
Lim Уровень 41
18 февраля 2015
6. -> «результут»
Очепятка, исправьте.
Спасибо за статью