На любом интервью, для сеньоров или джуниоров, для опытных или начинающих, вы столкнётесь с парой вопросов о Thread, параллелизме и многопоточности. Фактически эта встроенная поддержка параллелизма является одним из сильнейших достоинств Java и помогла ей достичь популярности равно среди предпренимателей и программистов. Большая часть прибыльных позиций Java разработчика требуют превосходного навыка многопоточности и опыта разработки, отладки и настройки высокопроизводительных приложений с низкой задержкой.
Поэтому это один из самых востребованых навыков на интервью. В типичном Java интервью интервьюер медленно начинает с базовых концептов Thread, задавая вопросы, такие как зачем нужны Thread, как их создать, какой способ создания лучше, наследованием от Thread или реализацией Runnable, и затем медленно переходит к трудностям параллелизма, трудностям, встречающимся при разработке параллельных приложений, высокоуровневым утилитам параллелизма, представленным в JDK 1.5, принципам и дизайн паттернам параллельных приложений и классическим проблемам многопоточности. Так как не достаточно просто знать азы многопоточности, вы должны знать, как справляться с проблемами параллелизма, такими как взаимная блокировка, состояние гонки, несогласованность памяти и различные проблемы Thread-безопасности.
Эти навыки тщательно тестируются, представляя различные проблемы многопоточности и параллелизма. Многие Java разработчики обычно просто прочитывают вопросы, перед самим интервью, что не плохо, но вам стоит разбираться в этом. Также накопление вопросов и выполнение одинаковых упражнений тратит очень много времени, поэтому я создал этот список.
Что такое поток в Java?
Thread – это независимый путь выполнения. Её цель – воспользоваться преимуществом нескольких процессоров, доступных в машине. Используя несколько потоков, вы можете ускорить задачи, привязанные к процессору. Например, если одному потоку нужно 100 миллисекунд на выполнение работы, вы можете использовать 10 Thread, чтобы сократить эту работу до 10 миллисекунд. Java предоставляет отличную поддержку многопоточности на уровне языка и это, к тому же, одно из сильнейших её достоинств.
Различие между Thread и процессами в Java?
Поток – это подмножество процесса, другими словами один процесс может содержать множество потоков. Два процесса исполняются на различных пространствах памяти, но все Thread делят одно пространство. Не спутайте это с памятью стека, которая различная для каждого потока и используется для хранения локальных данных этого потока.
Как создать Thread?
На уровне языка есть два способа создания Thread. Объект класса java.lang.Thread представляет собой Thread, но ей требуется задача для исполнения, которая является объектом, реализующим интерфейс java.lang.Runnable. Так как класс Thread реализует интерфейс Runnable, вы можете переопределить метод run() унаследовав ваш класс от Thread или реализовав в нём интерфейс Runnable.
Когда использовать Runnable и когда Thread?
Это дополнение к предудыщему вопросу. Как мы знаем, Thread можно создать унаследовавшись от класса Thread или реализовав интерфейс Runnable. Возникает вопрос, который из способов лучше и когда какой использовать? На этот вопрос легко ответь, если вы знаете, что Java не поддерживает множественное наследование классов, но позволяет реализовывать множество интерфейсов. Что означает, что лучше реализовывать Runnable, если вы хотите унаследоваться от другого класса.
Разница между методами start() и run()?
Один из вопросов с подвохом из прошлого, но он всё ещё достаточно хорош, чтобы отличить поверхностное понимание многопоточности в Java. Метод start() используется для запуска нового потока. Несмотря на то, что start() вызывает метод run() внутри себя, это не то же самое, что просто вызвать run(). Если вы вызываете run() как обычный метод, он вызывается в той же Thread и никакая новая Thread не запуститься, что происходит, когда вы вызываете метод start().
Различия Runnable и Callable?
Оба интерфейса представляют задачи, которые предназначены для выполнения в отдельных потоках. Runnable существует ещё с JDK 1.0, а Callable был добавлен в JDK 1.5. Главное их различие заключается в том, что метод call() у Callable может возвращать значения и выбрасывать исключения, что невозможно в методе run() у Runnable. Callable возвращает объект Future, который может содержать результат вычислений.
Различия между CyclicBarrier и CountDownLatch?
Хоть оба эти синхронизаторы позволяют потокам дожидаться друг друга, главное различие между ними в том, что вы не можете заново использовать CountDownLatch после того, как его счётчик достигнет нуля, но вы можете использовать CyclicBarrier снова, даже после того, как барьер сломается.
Что такое модель памяти Java?
Модель памяти – это набор правил и указаний, которые позволяют Java программам действовать детерминировано среди множества архитектур памяти, процессора и операционной системы. Это особенно важно в случае многопоточности. Модель памяти предоставляет гарантии того, что изменения, произведённые одним потоком, будут видны для других, одна из них – отношение happens-before (случалось ранее). Это отношение определяет несколько правил, которые позволяют программистам предвидеть и определять поведение параллельных программ. Например happens-before гарантирует:
- Каждое действие в потоке случается раньше любого действия в этом потоке, которое следует в программном порядке, это также известно, как правило программного порядка.
- Разблокировка монитора случается раньше каждой последующей блокировки того же самого монитора, также известно, как правило блокировки Монитора.
- Запись volatile поля случается раньше каждого последующего чтения этого поля, правило изменчивой переменной.
- Вызов Thread.start() в потоке случается раньше, чем любой другой поток замечает, что поток был остановлен, либо после удачного Thread.join(), либо если Thread.isAlive() возвращает false, правило Thread.start().
- Прерывание потока из другого потока случается раньше, чем прерванный поток заметит прерывание (либо от выброса InterruptedException, либо от проверки isInterrupted()), правило прерывания потока.
- Окончание конструктора объекта случается раньше, чем запуск финализатора для этого объекта, правило Финализатора.
- Если А случается раньше В, и В случается раньше С, значит А случается раньше С, что значит happens-before гарантирует транзитивность.
Что такое volatile переменная?
Volatile – специальный модификатор, который может применяться только к атрибутам. В параллельных Java программах изменения, произведённые различными потоками на атрибутах, не видны для остальных при отсутствии синхронизатора. Volatile переменная гарантирует, что запись будет осуществляться до последующего чтения, что сказано в правиле изменчивой переменной в предыдущем вопросе.
Что такое поток-безопасность? Безопасный ли класс Vector?
Поток-безопасность – свойвство объекта или кода, которое гарантирует, что при исполнении или использовании несколькими потоками, код будет вести себя, как предполагается. Например поток-безопасный счётчик не пропустит ни один счёт, если тот же экземпляр счётчика используется среди нескольких потоков. Очевидно, можно разделить классы коллекций на две категории, поток-безопасные и не-поток-безопасные. Vector поток-безопасен и достигает этого синхронизацией методов, которые изменяют состояние Vector'a, с другой стороны его коллега ArrayList не-поток-безопасен.
Что такое состояние гонки (race condition)?
Состояние гонки – причина трудноуловимых багов. Как сказано в самом названии, состояние гонки возникает из-за гонки между несколькими потоками, если поток, который должен исполнятся первым, проиграл гонку и исполняется второй, поведение кода изменяется, из-за чего возникают недетерменированные баги. Это одни из сложнейших к отлавливанию и воспроизведению багов, из-за беспорядочной природы гонок между потоками. Пример состояния гонки – беспорядочное исполнение.
Как остановить поток?
Я всегда говорил, что Java предоставляет богатые API для всего, но, по иронии судьбы, не предоставляет удобных способов остановки потока. В JDK 1.0 было несколько управляющих методов, например stop(), suspend() и resume(), которые были помечены как deprecated в будущих релизах из-за потенциальных угроз взаимной блокировки, с тех пор разработчики Java API не предприняли попыток представить стойкий, поток-безопасный и элегантный способ остановки потоков. Программисты в основном полагаются на факт того, что поток останавывается сам, как только заканчивает выполнять методы run() или call(). Для остановки вручную, программисты пользуются преимуществом volatile boolean переменной и проверяют её значение в каждой итерации, если в методе run() есть циклы, или прерывают потоки методом interrupt() для внезапной отмены заданий.
Что происходит, когда в потоке появляется исключение?
Это один из хороших вопросов с подвохом. Простыми словами, если исключение не поймано – поток мерта, если установлен обработчик непойманных исключений, он получит колбек. Thread.UncaughtExceptionHandler – интерфейс, определённый как вложенный интерфейс для обработчиков, вызываемых, когда поток внезапно останавливается из-за непойманного исключения. Когда поток собирается остановится из-за непойманного исключения, JVM проверит её на наличие UncaughtExceptionHandler, используя Thread.getUncaughtExceptionHandler(), и вызовет у обработчика метод uncaughtException(), передав поток и исключение в виде аргументов.
Как поделиться данными между двумя потоками?
Вы можете делиться данным между потоками, используя общий объект или параллельные структуры данных, типа BlockingQueue.
Различия между notify и notifyAll?
Это ещё один из вопросов с подвохом, так как за одним монитором могут наблюдать несколько потоков, Java API разработчики предоставляют метод для уведомления об изменении его состояния только одной или сразу всех потоков, но они предоставляют только половину реализации. У метода notify() не реализован способ выбора определённого потока, поэтому он полезен только когда вы точно знаете, что всего один поток ожидает. С другой стороны, notifyAll() уведомляет все потоки и позволяет им побороться за монитор, что гарантирует, что по крайней мере один поток продвинется дальше.
Почему wait, notify и notifyAll не в классе Thread?
Это вопрос, относящийся к дизайну, который проверяет, что кандидат думает о существующих системах или думал ли он когда-либо о чём-то схожем, но выглядящем неуместно поначалу. Чтобы ответить на этот вопрос, вам нужно предоставить несколько причин, почему эти методы удобнее реализовывать в классе Object, и почему не в классе Thread. Первая очевидная причина – Java поддерживает lock на уровне объектов, а не на уровне потоков. Любой объект имеет lock, который получает поток. И если потоку нужно ждать определённый lock, есть смысл в том, чтобы вызвать wait() на объект, чем на этот поток. Если бы wait() был объявлен в классе Thread, было бы не ясно, какой lock поток ждёт. Вкратце, так как wait, notify и notifyAll работают на уровне lock, удобнее объявить их в классе Object, потому что lock относится к объекту.
Что такое ThreadLocal переменная?
ThreadLocal переменные – специальный вид переменных, доступных Java программисту. Так же, как для состояний есть переменная состояния, для потоков есть ThreadLocal переменные. Это неплохой способ достичь поток-безопасности для затратных-для-создания объектов, например вы можете сделать SimpleDateFormat поток-безопасным, используя ThreadLocal. Так как это затратный класс, его нежелательно использовать в локальной области, которая требует отдельных экземпляров на каждый вызов. Предоставляя каждому потоку её собственную копию, вы убиваете двух зайцев. Во-первых, вы уменьшаете количество экземпляров затратных объектов, используя по новой фиксированное количество экземпляров, и во-вторых, вы достигаете поток-безопасности, без потерь синхронизации и неизменяемости. Ещё один хороший пример локальной переменной у потока – класс ThreadLocalRandom, который уменьшает количество экземпляров затратных-для-создания объектов Random в много-поточной среде.
Что такое FutureTask?
FutureTask представляет собой отменяемое асинхронное вычисление в параллельном Java приложении. Этот класс предоставляет базовую реализацию Future, с методами для запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения результатов. Результат может быть получен только когда вычисление завершено, метод получения будет заблокирован, если вычисление ещё не завершено. Объекты FutureTask могут быть использованы для обёртки объектов Callable и Runnable. Так как FutureTask реализует Runnable, его можно передать Executor'у на выполнение.
Различие между interrupted и isInterrupted?
Основное различие между interrupted() и isInterrupted() в том, что первый сбрасывает статус прерывания, а второй нет. Механизм прерывания в Java реализован с использованием внутреннего флага, известного как статус прерывания. Прерывание потока вызовом Thread.interrupt() устанавливает этот флаг. Когда прерванный поток проверяет статус прерывания, вызывая статический метод Thread.interrupted(), статус прерывания сбрасывается. Нестатический метод isInterrupted(), который используется потоком для проверки статуса прерывания у другого потока, не изменяет флаг прерывания. Условно, любой метод, который завершается, выкинув InterruptedException сбрасывает при этом флаг прерывания. Однако, всегда существует возможность того, что флаг тут же снова установится, если другой поток вызовет interrupt().
Почему методы wait и notify вызываются в синхронизированном блоке?
Основная причина вызова wait и notify из статического блока или метода в том, что Java API обязательно требует этого. Если вы вызовете их не из синхронизированного блока, ваш код выбросит IllegalMonitorStateException. Более хитрая причина в том, чтобы избежать состояния гонки между вызовами wait и notify.
Почему вы должны проверять состояние ожидания в цикле?
Существует возможность того, что ожидающий поток получит ложные предупреждения и ложные вызовы пробуждения, если он не проверит состояние ожидания в цикле, он просто выйдет, даже если состояние не достигнуто. Когда ожидающий поток пробуждается, он не думает о том, что состояние, которое он ожидал, может все ещё оставаться в силе. Оно могло быть действительно в прошлом, но потом быть изменено после вызова метода notify() и перед тем как поток пробудился. Поэтому всегда лучше вызывать wait() из цикла.
Различия между synchronized и concurrent коллекциями?
Хоть обе synchronized и concurrent коллекции предоставляют поток-безопасные коллекции, последняя является более масштабируемой. До Java 1.5 программистам были доступны только synchronized коллекции, которые становились источником раздора, когда несколько потоков обращались к ним одновременно, что затрудняло масштабирование системы. Java 5 представила concurrent коллекции, например ConcurrentHashMap, которые не только предоставляют поток-безопасность, но также улучшают масштабируемость, используя современные техники, такие как lock stripping и partitioning internal table.
Различия между Стеком и Кучей?
Почему этот вопрос присутствует в вопросах о много-поточности? Потому что стек – участок памяти, тесно связанный с потоками. У каждого потока есть свой стек, которые хранит локальные переменные, параметры методов и стек вызовов. Переменная, хранящаяся в стеке одного потока, не видна для другого. С другой стороны, куча – общий участок памяти, который делится между всеми потоками. Объекты, неважно локальные или любого другого уровня, создаются в куче. Для улучшения производительности, поток обычно кэширует значения из кучи в свой стек, тут-то выползают volatile переменные. Volatile указывает потокам на то, что переменную нужно читать из главной памяти.
Что такое пул потоков?
Создание потока затратно в плане времени и ресурсов. Если вы создаёте поток во время обработки запроса, это замедлит время отклика, также процесс может создать только ограниченное число потоков. Чтобы избежать этих проблем, во время запуска приложения создаётся пул потоков и потоки повторно используются для обработки запросов. Этот пул потоков называется «thread pool», а потоки в нём – рабочие потоки. Начиная с Java 1.5 Java API предоставляет фреймворк Executor, который позволяет вам создавать различные пулы потоков, например single thread pool, который обрабатывает только одно задание за единицу времени, fixed thread pool, пул с фиксированным количеством потоков, и cached thread pool, расширяемый пул, подходящий для приложений с множеством недолгих заданий.
Как решить Producer Consumer проблему?
Большинство поточных проблем, которые вы решаете в реальности, из категории паттерна Producer Consumer, в котором один поток порождает задачу, а второй поглощает её. Вам нужно знать, как построить внутренние взаимодействия потоков, для решения этой проблемы. На низком уровне вы можете воспользоваться методами wait и notify, а на высоком уровне вы можете воспользоваться преимуществами Semaphore или BlockingQueue
Как избежать взаимной блокировки (deadlock)?
![Перевод: Топ-50 интервью вопросов по потокам. Часть 1. - 1]()
Deadlock – состояние, в котором поток ждёт, пока второй поток совершит какое-либо действие, а второй, в это же время, ждёт того же от первого. Это очень серьёзная проблема, из-за которой ваша программа зависает и не делает того, для чего она предназначена. Deadlock происходит, когда достигаются эти 4 состояния:
- Взаимное исключение: по крайней мере один ресурс должен быть занят в режиме неделимости. Только один процесс может использовать ресурс в любой данный момент времени.
- Удержание и ожидание: процесс удерживает как минимум один ресурс и запрашивает дополнительных ресурсов, которые удерживаются другими процессами.
- Нет пред-очистке: операционная система не переназначивает ресурсы, если они уже заняты, они должны отдаваться удерживающим процессом добровольно.
- Цикличное ожидание: процесс ждёт освобождения ресурсов другим процессом, который в свою очередь ждёт освобождения ресурсов первым процессом.
Простейший способ избежать взаимной блокировки – не допускать цикличного ожидания, этого можно достичь, получая lock'и в определённом порядке и освобождая их в обратном порядке.
Различия между livelock и deadlock?
Livelock схож с deadlock, только в livelock состояния потоков или вовлечённых процессов постоянно изменяются в зависимости друг от друга. Livelock – особый случай нехватки ресурсов. Реальный пример livelock'а – когда два человека встречаются в узком коридоре и каждый, пытаясь быть вежливым, отходит в сторону, и так они бесконечно двигаются из стороны в сторону.
Как проверить, удерживает ли поток lock?
Я и не подозревал, что можно проверять, удерживает ли поток lock в данный момент, до тех пор, пока не столкнулся с этим вопросом в одном телефонном интервью. В java.lang.Thread есть метод holdsLock(), он возвращает true, тогда и только тогда, когда текущий поток удерживает монитор у определённого объекта.
Как получить дамп потока?
Дамп потока позволяет узнать, чем поток занимается в данный момент. Существует несколько способов получения дампа потока, зависящих от операционной системы. В Windows вы можете использовать комбинацию ctrl + Break, в Linux – команду kill -3. Также вы можете воспользоваться утилитой jstack, она оперирует над id процесса, который вы можете узнать с помощью другой утилиты jps.
Какой JVM параметр используется для контроля размера стека потока?
Это один из простых, -Xss параметер используется для контроля размера стека потока в Java.
Различия между synchronized и ReentrantLock?
Были времена, когда единственный способ достижения взаимного исключения был через ключевое слово synchronized, но он имеет несколько недостатков, например нельзя расширить lock за пределами метода или блока кода и т.д. Java 5 решает эту проблему, предоставляя более утончённый контроль через интерфейс Lock. ReentrantLock – распространённая реализация Lock, которая предоставляет Lock с таким же базовым поведением и семантикой, как у неявного монитора, достигаемый использованием синхронизированных методов, но с расширенными возможностями.
Даны 3 потока Т1, Т2 и Т3? Как реализовать последовательность Т1, Т2, Т3?
Последовательности можно достичь многими способами, но вы можете просто воспользоваться методом join(), чтобы запустить поток, когда другой закончит своё выполнение. Для реализации заданной последовательности, вам нужно запустить последний поток первым, и затем вызывать метод join() в обратном порядке, то есть Т3 вызывает Т2.join, а Т2 вызывает Т1.join, таким образом Т1 закончит выполнение первым, а Т3 последним.
Что делает метод yield?
Метод yield – один из способов попросить поток уступить процессор, чтобы другой мог выполниться. Это статический метод и он только гарантирует, что текущий поток уступит процессор, но не решает, к какому потоку перейдёт исполнение.
Какой уровень параллелизма у ConcurrentHashMap?
ConcurrentHashMap достигает своей масштабируемости и поток-безопасности, разбивая действительную map на секции. Это разделение достигается использованием уровня параллелизма. Это опциональный параметр конструктора ConcurrentHashMap и его значение по-умолчанию 16.
Что такое Semaphore?
Semaphore – это новый тип синхронизатора. Это семафор со счётчиком. Концептуально, семафор управляет набором разрешений. Каждый acquire() блокируется, если необходимо, до того, как разрешение доступно, затем получает его. Каждый release() добавляет разрешение, потенциально освобождая блокирующий получатель (acquirer). Однако при этом не используются фактические объекты разрешений; Semaphore просто хранит количество доступных и действует соответственно. Semaphore используется для защиты дорогих ресурсов, которые доступны в ограниченном количестве, например подключение к базе данных в пуле.
Что будет, если очередь пула потоков уже заполнена, а вы подадите задачу?
Если очередь пула потоков заполнилась, то поданная задача будет «отклонена». Метод submit() у ThreadPoolExecutor'а выкидывает RejectedExecutionException, после чего вызывается RejectedExecutionHandler.
Различия между методами submit() и execute() у пула потоков?
Оба метода являются способами подачи задачи в пул потоков, но между ними есть небольшая разница. Execute(Runnable command) определён в интерфейсе Executor и выполняет поданную задачу в будущем, но, что более важно, ничего не возвращает. С другой стороны submit() – перегруженный метод, он может принимать задачи типов Runnable и Callable и может возвращать объект Future, который можно использовать для отмены выполнения и/или ожидания результата вычислений. Этот метод определён в интерфейсе ExecutorService, который наследуется от интерфейса Executor, и каждый класс пула потоков, например ThreadPoolExecutor или ScheduledThreadPoolExecutor, наследует эти методы.
Что такое блокирующий метод?
Блокирующий метод – метод, который блокируется, до тех пор, пока не выполнится задание, например метод accept() у ServerSocket блокируется в ожидании подключения клиента. Здесь блокирование означает, что контроль не вернётся к вызывающему методу до тех пор, пока не выполнится задание. С другой стороны, существуют асинхронные или не блокирующиеся методы, которые завершаются до выполнения задачи.
Поток-безопасен ли Swing?
Проще говоря, нет, Swing не поток-безопасен, но вам нужно объяснить, что вы этим имеете в виду, даже если интервьюер не спросил об этом. Когда мы говорим, что Swing не поток-безопасен, мы обычно ссылаемся на то, что это компонент, который не может быть модифицирован несколькими потоками. Все изменения GUI компонентов должны быть сделаны в AWT потоке, и Swing предоставляет синхронные и асинхронные методы для планирования таких изменений.
Различия между invokeAndWait и invokeLater?
Это два метода Swing API, которые позволяют разработчикам обновлять GUI компоненты из потоков, а не из потока Диспетчера Событий. InvokeAndWait() синхронно обновляет GUI компонент, например полоска прогресса, каждый раз когда достигнут прогресс, полоска должна быть обновлена, чтоб отразить изменения. Если прогресс отслеживается в другом потоке, он должен вызывать invokeAndWait() для назначения обновления этого компонента потоком Диспетчера Событий. А invokeLater() – асинхронный вызов обновления компонентов.
Какие методы Swing API поток-безопасны?
Этот вопрос снова о Swing и поток-безопасности, хоть компоненты Swing не поток-безопасны, существуют методы, которые можно безопасно вызвать из нескольких потоков. Я знаю, что repaint() и revalidate() поток-безопасны, но есть и другие методы на разных Swing компонентах, например setText() у JTextComponent, методы insert() и append() в классе JTextArea.
Как создать неизменяемые объекты?
Этот вопрос может показаться не имеющим ничего общего с многопоточностью и параллелизмом, но он имеет. Неизменяемость помогает упростить и без того сложный параллельный код. Неизменяемый объект очень дорог для разработчиков, так как он может распространятся без какой-либо синхронизации. К сожалению в Java нет аннотации @Immutable, которая сделает ваш объект неизменяемым, для этого разработчикам нужно попотеть. Чтобы создать неизменяемый объект, нужно следовать основам: инициализация в конструкторе, отсутствие сеттеров, отсутствие утечки ссылки, хранить отдельно копии изменяемых объектов.
Что такое ReadWriteLock?
В целом, ReadWriteLock – это результат техники разбора lock'а для улучшения производительности параллельных приложений. Это интерфейс, который был добавлен в Java 5. Он оперирует парой связанных lock'ов, один для операций чтения, один для записи. Читающий lock может удерживаться одновременно несколькими читающими потоками, до тех пор пока не будет записывающих. Записывающий lock эксклюзивен. Если хотите, вы можете реализовать интерфейс с вашим набором правил, или вы можете использовать ReentrantReadWriteLock, который поддерживает максимум 65535 рекурсивных записывающих lock'ов и 65535 читающих lock'ов.
Что такое busy spin?
Busy Spin – это техника, которую программисты используют, чтобы заставить поток ожидать при определённом условии. В отличие от традиционных методов, wait(), sleep() или yield(), которые подразумевают уступание контроля над процессором, этот метод не уступает процессор, вместо этого он просто выполняет пустой цикл. Зачем кому-то делать это? Чтобы сохранить кэш процессора. В многоядерных системах, существует возможность, что приостановленный поток продолжит своё выполнение на другом ядре, что означает перестройку кэша. Чтобы избежать затратной перестройки, программист предпочитает ждать меньше, используя busy spin.
Различия между volatile и atomic переменными?
Это довольно интересный вопрос, сперва volatile и atomic переменные выглядят очень похоже, но всё же они разные. Volatile переменная предоставляет happens-before гарантию, что запись будет производится до любой последующей записи, она не гарантирует атомарности. Например, операция count++ не станет атомарной просто потому что count объявлена volatile. C другой стороны class AtomicInteger предоставляет атомарный метод для выполнения таких сложных операций атомарно, например getAndIncrement() – атомарная замена оператора инкремента, его можно использовать, чтобы атомарно увеличить текущее значение на один. Также существуют атомарные версии и для других типов данных.
Что случится, если поток выбросит Исключение в синхронизированном блоке?
Это ещё один вопрос с подвохом для обычных Java программистов. Не важно, как вы выходите из синхронизированного блока, нормально, закончив выполнение, или внезапно, выбросив исключение, поток освобождает полученный lock, при входе в синхронизированный блок. Это одна из причин, почему я предпочитаю синхронизированный блок lock интерфейсу, который требует особого внимания при освобождении lock'а, обычно это достигается освобождением lock'а в блоке finally.
Что такое double checked locking Синглтона?
Это один из самых популярных вопросов на интервью, и всё же, несмотря на его популярность, шансы, что кандидат ответит на него, в лучшем случае 50%. В половине случаев они проваливаются при написании кода, а в другой половине при объяснении, как это было сломано и пофикшено в Java 1.5. Это старый способ создания поток-безопасного синглтона, который пытается оптимизировать производительность, блокируясь только когда экземпляр синглтона создаётся впервые, но из-за сложности и факта, что он был сломан в JDK 1.4, мне лично он не нравится. Но всё-таки, даже если вы не предпочитаете этот подход, полезно его знать с точки зрения интервью.
Как создать поток-безопасный Синглтон?
Этот вопрос дополняет предыдущий. Если вы скажете, что не любите double checked locking, то интервьюер будет вынужден спросить об альтернативных способах создания поток-безопасного Синглтона. И они есть, вы можете воспользоваться особенностями загрузки классов и инициализации статических переменных для создания экземпляра Синглтона, или вы можете использовать преимущества мощного типа перечислений.
Перечислите 3 обычая, которым вы следуете в параллельном программировании?
Это мой любимый вопрос, потому что я считаю, что нужно соблюдать определённые правила при написании параллельного кода, что помогает в производительности, дебаге и поддержке. Далее представлены 3 лучших правила, которые, я считаю, должен соблюдать каждый Java программист:
- Всегда давайте значимые имена своим потокам
Нахождение бага или отслеживание исключения в параллельном коде – довольно сложная задача. OrderProcessor, QuoteProcessor или TradeProcessor намного лучше, чем Thread-1. Thread-2 и Thread-3. Имя должно отражать задачу, выполняемую данным потоком. Все крупные фреймворки и даже JDK следуют этому правилу.
- Избегайте блокировки или уменьшите масштабы синхронизации
Блокировка затратна, а переключение контекста ещё более затратно. Пытайтесь избегать синхронизации и блокировка насколько это возможно, и в необходимом минимуме вы уменьшите критическую секцию. Поэтому я предпочитаю синхронизированный блок синхронизированному методу, потому что это даёт вам абсолютный контроль над масштабом блокировки.
- Между синхронизаторами и wait и notify, выбирайте синхронизаторы
Во-первых, синхронизаторы, типа CountDownLatch, Semaphore, CyclicBarrier или Exchanger упрощают кодинг. Очень сложно реализовывать сложный управляющий поток, используя wait и notify. Во-вторых, эти классы написаны и обслуживаются лучшими в бизнесе и есть большой шанс, что они будут оптимизированы или заменены более лучшим кодом в последующих релизах JDK. Используя утилиты синхронизации высокого уровня, вы автоматически получаете все эти преимущества.
- Между Concurrent Collection и Synchronized Collection, выбирайте Concurrent Collection
Это ещё одно простое правило, которому легко следовать и пожинать выгоды. Concurrent колекции более масштабируемые, чем их синхронизрованые коллеги, поэтому при написании параллельного кода лучше использовать их. Так что в следующий раз, когда вам понадобится map, подумайте о ConcurrentHashMap прежде, чем подумаете о Hashtable.
- Всегда давайте значимые имена своим потокам
Как вынужденно запустить поток?
Это вопрос а ля как вынужденно запустить сборку мусора. Коротко, никак, вы, конечно, можете сделать запрос, используя System.gc(), но он ничего не гарантирует. В Java нет абсолютно никакого способа вынужденного запуска потока, это контролируется планировщиком потоков и Java не предоставляет никакой API для его контроля. Это часть Java всё ещё рандомна.
Что такое фреймворк Fork/Join?
Фреймворк Fork/Join, представленный в JDK 7, - это мощная утилита, позволяющая разработчику пользоваться преимуществами нескольких процессоров у современных серверов. Он разработан для работы, которую можно рекурсивно разбить на маленькие частицы. Цель – использовать всю доступную вычислительную мощь, для увеличения производительности вашего приложения. Одного значительное преимущество этого фреймворка в том, что он использует work-stealing алгоритм (от work – работа и steal – красть). Рабочие потоки, у которых закончились свои задания, могут «своровать» задания у других потоков, которые всё ещё заняты.
В чём разница вызовов методов wait() и sleep()?
Хотя и wait, и sleep представляют подобие паузы в Java приложении, это устройства для различных нужд. Wait используется для внутренней коммуникации потоков, он уступает lock, если условие ожидание истинно, и ждёт уведомления, если из-за действий другого потока условие ожидания ложно. С другой стороны метод sleep() просто уступает процессор или останавливает выполнение текущего потока на заданное время. Вызов sleep() не освобождает удерживаемый текущим потоком lock.

ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
KapChook может лучше полям, а не атрибутам?