JavaRush /Blog Java /Random-VI /Bản dịch: 50 câu hỏi phỏng vấn hàng đầu theo chủ đề. Phần...
KapChook
Mức độ
Volga

Bản dịch: 50 câu hỏi phỏng vấn hàng đầu theo chủ đề. Phần 1.

Xuất bản trong nhóm
Phần đầu tiên dịch của bài viết gốc Top 50 câu trả lời phỏng vấn về Java Thread dành cho người mới vào nghề, lập trình viên có kinh nghiệm. Phần thứ hai. Lưu ý: bài viết có kích thước lớn nên không phù hợp với một chủ đề. Ngoài ra, nó khá phức tạp, tôi đã cố gắng hết sức để google nó nhưng vẫn vậy. Vì vậy, chúng tôi yêu cầu những người tham gia thông thạo tiếng Anh xem bản gốc và so sánh với bản dịch, đề phòng trường hợp họ hiểu nhầm điều gì đó hoặc dịch sai. Cảm ơn bạn trước. Trong bất kỳ cuộc phỏng vấn nào, cấp cao hay cấp dưới, người có kinh nghiệm hay người mới bắt đầu, bạn sẽ phải đối mặt với một số câu hỏi về chủ đề, tính song song và đa luồng. Trên thực tế, sự hỗ trợ tích hợp sẵn cho hoạt động đồng thời này là một trong những điểm mạnh nhất của Java và đã giúp nó trở nên phổ biến trong giới doanh nhân cũng như lập trình viên. Hầu hết các vị trí nhà phát triển Java sinh lợi đều yêu cầu kỹ năng đa luồng xuất sắc và kinh nghiệm phát triển, gỡ lỗi và điều chỉnh các ứng dụng hiệu suất cao, độ trễ thấp. Vì vậy, đây là một trong những kỹ năng được tìm kiếm nhiều nhất trong các cuộc phỏng vấn. Trong một cuộc phỏng vấn Java điển hình, người phỏng vấn bắt đầu từ từ với các khái niệm cơ bản về luồng, đặt các câu hỏi như tại sao cần có luồng, cách tạo chúng, cách tạo chúng nào tốt hơn, kế thừa từ Thread hoặc triển khai Runnable, sau đó từ từ di chuyển về những khó khăn của tính đồng thời, những khó khăn gặp phải khi phát triển các ứng dụng song song, các tiện ích đồng thời cấp cao được giới thiệu trong JDK 1.5, các nguyên tắc và mẫu thiết kế của các ứng dụng song song cũng như các vấn đề đa luồng cổ điển. Chỉ vì chỉ biết những điều cơ bản về đa luồng là chưa đủ mà bạn phải biết cách xử lý các vấn đề tương tranh như bế tắc, điều kiện chạy đua, sự không nhất quán về bộ nhớ và các vấn đề an toàn luồng khác nhau. Những kỹ năng này đã được kiểm tra kỹ lưỡng, đưa ra nhiều thách thức đa luồng và đồng thời khác nhau. Nhiều nhà phát triển Java thường chỉ đọc câu hỏi trước cuộc phỏng vấn, đây không phải là điều xấu, nhưng bạn nên hiểu điều này. Ngoài ra, việc tích lũy các câu hỏi và làm các bài tập giống nhau sẽ lãng phí rất nhiều thời gian, vì vậy tôi đã tạo ra danh sách này.
  1. Chuỗi trong Java là gì?

  2. Một luồng là một đường dẫn thực thi độc lập. Mục tiêu của nó là tận dụng lợi thế của nhiều bộ xử lý có sẵn trong máy. Bằng cách sử dụng nhiều luồng, bạn có thể tăng tốc các tác vụ liên quan đến CPU. Ví dụ: nếu một luồng mất 100 mili giây để hoàn thành một công việc, bạn có thể sử dụng 10 luồng để giảm công việc đó xuống còn 10 mili giây. Java cung cấp sự hỗ trợ tuyệt vời cho đa luồng ở cấp độ ngôn ngữ và hơn nữa, đây là một trong những lợi thế mạnh nhất của nó.
  3. Sự khác biệt giữa các luồng và tiến trình trong Java?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  52. Bản dịch: 50 câu hỏi phỏng vấn hàng đầu theo chủ đề.  Phần 1. - 1 Deadlock – состояние, в котором нить ждёт, пока вторая нить совершит Howое-либо действие, а вторая, в это же время, ждёт того же от первой. Это очень серьёзная проблема, из-за которой ваша программа зависает и не делает того, для чего она предназначена. Deadlock происходит, когда достигаются эти 4 состояния:
    • Взаимное исключение: по крайней мере один ресурс должен быть занят в режиме неделимости. Только один процесс может использовать ресурс в любой данный момент времени.
    • Удержание и ожидание: процесс удерживает How минимум один ресурс и запрашивает дополнительных ресурсов, которые удерживаются другими процессами.
    • Нет пред-очистке: операционная система не переназначивает ресурсы, если они уже заняты, они должны отдаваться удерживающим процессом добровольно.
    • Цикличное ожидание: процесс ждёт освобождения ресурсов другим процессом, который в свою очередь ждёт освобождения ресурсов первым процессом.
    Cách đơn giản nhất để tránh deadlock là tránh phải chờ đợi trong một vòng lặp; điều này có thể đạt được bằng cách lấy các khóa theo một thứ tự nhất định và giải phóng chúng theo thứ tự ngược lại.
  53. Sự khác biệt giữa livelock và bế tắc?

  54. Livelock tương tự như deadlock, chỉ có điều trong livelock trạng thái của các luồng hoặc tiến trình liên quan là liên tục thay đổi tùy thuộc vào nhau. Livelock là một trường hợp đặc biệt của tình trạng thiếu tài nguyên. Một ví dụ thực tế về livelock là khi hai người gặp nhau trong một hành lang hẹp và mỗi người cố gắng tỏ ra lịch sự, bước sang một bên và cứ thế họ di chuyển không ngừng từ bên này sang bên kia.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION