Взято с http://www.rsdn.ru/forum/java/3622844.1
Все проекты связанные с многопоточностью я бы разделил на 3 класса: использующие многопоточность, основанные на многопоточности и те, которые и есть сама многопоточность. К первому классу("использующие") я бы отнес проекты, которые предполагают работу в многопоточной среде. Пример: есть класс BlaBlaBlaFactory, в документации необходимо указать, может ли эта фабрика использоваться одновременно несколькими потоками или нет. Если нет, то сделать ее потокобезопасной с помощью ReentrantLock. Ко второму классу ("основанные на") я бы отнес проекты, в которых использование нескольких потоков является одним из ключевых моментов. Пример: многопоточный кодек видео формата H.264. К третьему классу ("являются ей") я бы отнес проекты, в корне что-то меняющие в отношении потоков. Пример: написание runtime-среды для языка Scala (на Java) с реализацией легковесных потоков и своеобразной моделью памяти.
Так вот, дело в том, что разработчик зачастую указывает на свое знание java concurrency имея в виду первый уровень. Компания же может иметь в виду второй (написание высоконагруженного сервера, написание физического/AI ядра 3D-шутера, написание OLAP-системы — во всех случаях с требованием ОДИН запрос пользователя обрабатывать в НЕСКОЛЬКО потоков). Иногда разработчики не знают про существование уровней выше первого. Особенную лепту в это вносят книги вводного или обзорного характера.
Ниже я привожу ряд вопросов с гипотетического собеседования по java concurrency, относящиеся как к первому так и ко второму уровню. Надеюсь кому-то они покажут направление, в котором возможно развиваться.
Как и на всяком собеседовании, не предполагается, что интервьюируемый ответит на все вопросы. Но предполагается, что опрашиваемый по-крайней мере ознакомлен с тематикой. Кроме того определение того 1) что кандидат знает; 2) о чем слышал; 3) о чем не слышал; позволяет составить о нем более полное мнение.
1. Назовите различия между Collections.synchronizedMap(new HashMap()) и ConcurrentHashMap.
2. Что такое кооперативная многозадачность и она ли ли в Java. Если да, то какие преимущества. Если нет, то какая тогда в Java?
3. Что такое "зеленый потоки" и они ли ли в Java (в HotSpot JVM.6)?
4. Различия в интерфейсах Runnable и Callable.
5. Напишите минимальный неблокирующий стек (всего два метода — push() и pop()).
6. Напишите минимальный copy-on-write ArrayList (всего четыре метода — void add(int indx, int item), int get(int indx), void remove(int indx), int size()).
7. Различя между Thread.isInterrupded() и Thread.interrupded().
8. Что происходит при вызове Thread.interrupt()?
9. Некоторые из следующих методов deprecated а некоторые и не были никогда реализованы. Какие? Thread.interrupt(), Thread.stop(), Thread.yield(), Thread.suspend(), Thread.resume(), Thread.join(), Thread.start(), Thread.run(), Thread.sleep().
10. Что Вы знаете о асинхронных вызовов методов? Есть ли это в самом языке Java? Если есть, то как реализовано? Если нет, то как бы Вы реализовали?
11. Перечислите ВСЕ причины по которым может выскочить InterruptedException.
12. Что изменилось между JMM до Java 5 и NewJMM после Java 5?
13. В классе String все поля финальные. Можно ли убрать ключевое слово финал? Ведь сеттеров все равно нет — следовательно поля нельзя переустановить.
14. Что такое ordering, visibility, atomicity, happend-before, mutual exclusion. И показать на примерах volatile, AtomicInteger, synchronize{} — что из вышеперечисленного списка присутствует и при каких вариантах использования.
15. Назовите отличия synchronize{} и ReentrantLock.
16. Что из данных вызовов создает happend-before: Thread.sleep(), Thread.join(), Thread.yield(), Thread.start(), Thread.run(), Thread.isAlive(), Thread.getState()?
17. Перечислите известные Вам способы борьбы с priority inversion, назовите классы систем где они особенно опасны.
18. Перечислите известные Вам способы 1)избежать 2)побороть возникшие deadlock-и (представьте, что вы пишете ядро RDBMS).
18.1. Что такое livelock?
19. Расскажите о паттернах Reactor/Proactor?
20. Что такое "monitor"?
21. Что такое "private mutex"?
22. Что такое "priority inheritance"?
23. Что такое "backoff protocol (expotential backoff)"?
24. Что такое "task stealing"?
25. Что такое "ABA problem"?
26. Что такое "test-end-set"?
27. Что такое "test-and-test-end-set"?
28. Что такое "spin lock"?
29. Что такое "sequential consistency"?
30. Что такое "sense-reversing barrier"?
31. Что такое "safe publication"?
32. Что это за свойство — "reentrancy"?
33. Что такое "recursive parallelism"?
34. Что такое "iterative parallelism"?
35. Что это за вариант архитектуры "pipeline"?
36. Что такое "poison message"?
37. Что такое "mutual exclusion"? Примеры как добиться в Java.
38. Что такое "condition waiting"? Примеры как добиться в Java.
39. Преимущества SheduledThreadPool перед java.util.Timer.
40. Различия между java.util.concurrent.Atomic*.compareAndSwap() и java.util.concurrent.Atomic*.weakCompareAndSwap().
41. Что в SynchronousQueue уникально для BlockingQueue.
42. Что такое "рандеву"? При помощи каких классов в Java его можно организовать?
43. Что такое "false sharing". Может ли происходит в Java. Если есть, то приведите примеры и как бороться. Если нет, то как побороли разработчики JVM.
44. Thread.getState() возвращает экземпляр Thread.State. Какие возможны значения?
45. Напишите простейший ограниченный буфер для многих производителей/многих потребителей с использованием synchronize{}. С использованием ReentrantLock.
46. У ReentrantLock созданного с аргументом true все же один из методов захвата блоктровки — не fair. Какой? Как это обойти?
47. Приведите наиболее существенное отличие между CountDownLatch и Barrier.
48. Что Вы знаете о Erlang? Что в нем есть существенного связанного с многопоточностью такого, чего нет в Java?
49. Что Вы знаете о CSP? Что в нем есть существенного связанного с многопоточностью такого, чего нет в Java?
50. Отличие Thread.start() и Thread.run()?
http://books.google.com.ua/books?id=2pKCnc6-UAEC&printsec=frontcover&hl=ru#v=onepage&q&f=false
http://docs.oracle.com/javase/7/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html
http://www.sei.cmu.edu/reports/10tr015.pdf
+ Мои собственные вопросы:
1*. Thread.currentThread() -- какой именно тред возвращается, если у нас многоядернная/процессорная среда?
2*. CopyOnWriteArrayList специфика.
3*. IllegalMonitorStateException. Когда вываливается и как избежать.
4*. Fork/Join. Что это и о чем это?
А теперь ответы:
1. Назовите различия между Collections.synchronizedMap(new HashMap()) и ConcurrentHashMap.
2*. CopyOnWriteArrayList специфика.
Collections.synchronizedMap(new HashMap()) это декоратор вокруг потоконебезопасной хештаблицы, который делает каждый метод сингхронным, что дает псевдо потокозащищенность, но при этом абсолютно ограничивает масштабиремость такого решения. При этом для удачной вставки, удаления и прохода по элементам, нам нужно вводит дополнительную синхронизацию.
ConcurrentHashMap - потокобезопасная реализация, позволяющая мапе быть хорошо масштабируемой. Мы можем паралельно писать/читать, проходить по элементам, блокировка ставится либо на каждый бакет(контейнер-буфер в массиве, в котором хранятся обьекты с одинаковым хешом), либо на определенный диапазон(группу) бакетов. ConcurrentModificationException никогда не будет выкинуто во время прохода итератором, проход будет наблюдать состояния массива на момент создания итератора(удаления старых и добавление новых после создания итератора для этого цикла остануться незамеченными). Такая мапа подходит для массива кешей, куда часто пишут и часто читают из, но плохо подходит для массива требующего возращения своей длинны или проверки на пустоту.
ConcurrentReaderHashMap отличается от предыдущей реализацией тем, что одновременно для разных потоков умеет предоставлять только процедуру чтения елементов, но записать в один момент времени может только один поток.
CopyOnWriteArrayList потокобезопасный ArrayList. Он более полезен в паралельных исчислениях, где обход по массиву значительно превосходит операции вставки и удаления элементов(но они присутсвуют и нам бы не хотелось в такие моменты подвешивать все потоки на момент изменения колекции). На момент изменения колекции эта реализация создает копию массива и изменяет копию, при этом на момент этой процедуры остальные потоки получают ссылку на стараю копию для своих итераторов, которая по просту не меняется, когда процедура изменения заканчивается, реализация начинате отдавать итератором уже ссылку на новую копию с изменениями, а старые ссылки спокойно себе заканчивают работу со старой копией. На момент отспускания последнего итератора на старую копию, она удаляется из памяти.
CopyOnWriteArraySet под капотом использует просто CopyOnWriteArrayList.
2. Что такое кооперативная многозадачность и она ли ли в Java. Если да, то какие преимущества. Если нет, то какая тогда в Java?
КМ - это многозадачность, при которой каждый поток отвечает сам за передачу процессорных ресурсов другим потокам. Другой подход это упреждающая многозадачность(УМ) - когда планировщик выделяет определенные кванты времени для занимания процессора/ядра, так гарантируется, что не будет полного зависания системы, в случае ошибки в одном отдельном потоке, который не передает процессор другим; а также то, что самые низкоприоритетные процессы гарантированно получат доступ к процессорным ресурсам.
Если мы посмотрим на методы класса Thread, то в нем есть методы для добровольного передавания процессорного рессурса и отправки "сообщений" в другие потоки, но поскольку в JVM стоит маппинг на системные треды, то реально все-таки приложение будет работать упреждающе(хоть при этом поток сам тоже будет отдавать процессорное время, его также будут заставлять это делать), настоящей кооперативности можно добиться в случае явного включения зеленных потоков, но вот вопрос нужно ли это? - при этом если писать Java приложение в стиле кооперативности, то оно таким и будет, потому что прерванный машиной поток все равно держит высокоуровневые мониторы, и другой поток приложения будет проверять именно эти мониторы и тоже не будет выполняться).
3. Что такое "зеленые потоки" и они ли ли в Java (в HotSpot JVM.6)?
"Зеленые потоки" - это реализация многототочности на уровне виртуальной машины, многопоточность какбы симулируется, а по факту JVM выполняется в одном нативном потоке. Эту идею принесла JVM 1.1, но потом со следующий версий по умолчанию JVM мапит java threads на нативные ОС потоки в соответсвии 1:1. Нативные потоки в старых ОС была довольно дорогая штука в плане переключения между ними, сейчас, я полагаю, дела обстоят получше. Зеленые потоки дают некоторые преимущества: во первых между ними переключения очень быстрые, потому что они на уровне юзерспейса памяти и виртуальная машина делает "свою быструю магию"; и программы написанные под многопоточность, попадая на ОС без реализации многопоточности на уровне ядра, работали себе ни о чем не подозревая(сейчас в новых JVM, я думаю, можно также решить проблему на таких ОС, только нужно явно указывать флаги для переключения виртуалки в этот режим).
4. Различия в интерфейсах Runnable и Callable.
Runnable передается для вызова его метода run либо в Thread, либо Executor. При этом метода не возвращает значения.
Callable возвращает со своего единственного метода call значение типа, который определяется в реализацией обобщения. Этот интерфейс мы можем передать в расширение интерфейса Executor ExecutorService, который привносит методы, которые могут принимать Callable, а возвращать Future, который предоставляет возможность определить закончилось ли выполнение в другом потоке, и получить по готовности результат, или подвесить текущий поток и разблокировать, когда выполнение второго будет закончено, вернув значение.
7. Различя между Thread::isInterrupded и Thread::interrupded.
_thread.isInterrupded() это не статический метод, поэтому он применяется на переменной в которой у нас находится конкретный поток, этот метод позволяет протестировать был ли этот поток прерван. Используется для проверки другого потока(не себя).
Thread.interrupded() статический, и теcтирует поточный активный тред - был ли он прерван.
8. Что происходит при вызове Thread::interrupt()?
_thread.interrupt() - нестатический метод, поэтому его мы вызываем обычно для другого потока из текущего.
Внутри JVM есть флаг при каждом треде "интерапт статус", именно он устанавливается в истину, если вызывается этот метод. Статический Thread.interrupded() сбрасывает для себя этот флаг, а нестатический thread.isInterrupded() - нет. Это реализовано для переборочного подхода(проверка флага в цикле), без выброса исключения. По конвенции любой метод, который заверщает свою работу исключением InterruptedException обязуется сбрасывать флаг "интерапт статус".
9. Некоторые из следующих методов deprecated, а некоторые и не были никогда реализованы. Какие? Thread.interrupt(), Thread.stop(), Thread.yield(), Thread.suspend(), Thread.resume(), Thread.join(), Thread.start(), Thread.run(), Thread.sleep().
_thread.interrupt() - это нестатический метод, применяется для генерирования исключения InterruptedException в потоке на который ссылается переменная, в идеале поток должен в своем run() ожидать такое исключение и подсыпать - передавая управление другим потокам.
_thread.stop() - deprecated, причина проста, метод подразумевает, что JVM scheduler(обвертка над ОС планировщиком или, если включена опция, планировщик зеленых потоков) попытается остановить поток как можно бытрее через исключение ThreadDeath, что подразумевает, что все блокировки будут отпущены на залоченных обьектах с их текущим состоянием на момент исключения(а значит - нестабильным), и вся "эта радость" станет доступна ждущим отпускания потокам.
Thread.yield() - статический, если дергатся статически, то подразумевается текущий поток. Это "уступить" означает, что поток говорит планировщику о своей готовности уступить CPU ресурсы для более "нужденных" процессов, если таких нет - то он готов продолжить свою работу немедленно. Обычно в бизнес приложениях это напрямую не используется, это для фреймворков высокоуровневых обьектов конкуренции.
_thread.suspend() - deprecated, причины те же, что и для стопа - это потенциально опасный механизм для дедлока. Он останавливает поток, пока не будет вызван метод резьюм(продолжить) - при этом подсыпание происходит мгновенно по решению с другого процесса, что означает в этот момент может быть занят лок, который должен быть свободен на случает попытки вызвать откуда-то resume, такой дедлок называется замароженный "процесс". Для безопасной реализации подобного подхода лучше пользоваться локом через _object.wait() throws InterruptedException и _object.notify()/notifyAll().
_thread.resume() - deprecated, все описано в suspend.
_thread.destroy() - deprecated, при этом он и не реализован.
10. Что Вы знаете о асинхронных вызовов методов? Есть ли это в самом языке Java? Если есть, то как реализовано? Если нет, то как бы Вы реализовали?
И еще один класс CompletableFuture появившийся в jdk1.8 расширяет Future и позволяет передавать колбеки, который будут выполнятся в потоке, который создает эту фичу, а вызываться по завершению потока фичи, реализуя интерфейса jdk1.8 CompletionStage.
11. Перечислите ВСЕ причины по которым может выскочить InterruptedException.
Нативные методы кидают из трек классов Thread, Object, Process.
1) Если поток А попытались присыпить из другого В, а третий С его прервал, то убаюкиватель В получить такое исключение в месте вызова sleep потока А.
2) Также если мы ждет от других потоков сообщение на разделенный монитор в поточном потоке, а этот поточный поток кто-то прервал, он будет пробужден этим исключением в месте ожидания монитора wait.
3) Если текущий поток А ждет завершение другого потока В, а его кто-то прервал, то он будет пробуждет исключением в месте вызова join потока В.
4) Ну и если мы запустили внешний процес, то мы можем подождать на его завершение, и если нас прервали мы опять таки пробуждаем в месте ожидания процесса waitFor.
Thread
.join() throws InterruptedException (void)
.join(long) throws InterruptedException (void)
.join(long, int) throws InterruptedException (void)
.sleep(long) throws InterruptedException (void)
.sleep(long, int) throws InterruptedException (void)
Object
.wait() throws InterruptedException (void)
.wait(long) throws InterruptedException (void)
.wait(long, int) throws InterruptedException (void)
Process (abstract class)
.waitFor() throws InterruptedException (int) - текущий поток переходит в спящее состояние до завершение подпроцесса представленного обьектом этого класса, по завершению получит код завершения. Или же будет из этого метода выкинуто исключение, если другой поток дернул у текущего метод interrupt().
.waitFor(long, Timeunit) throws InterruptedException (boolean) - вернется ложь, если подпроцесс не завершится до истечения указанного таймаута. Исключение вылитит при тех же условиях, что и в предыдущем методе.
Если мы коммуницируем между потоками с помощью синхронизированных методов на классах, а не вышеописанным инструментарием. То тогда будет полезно в таких сервисных классах, если операция длительная, периодически проверять на прервали ли нас, и тогда прерывать поточный поток, который захватил такой класс через его этот синхронизированный метод:
while(someFlagForComplitionAllPartitions()) {
somePartitionalJob();
if (Thread.interrupted()) // Clears interrupted status!
18.1. Что такое livelock?
Ситуация, которая возникает при неудачном просчете механизма разрешения дедлока. Потоки, которые попали в сцепку, одновременно пытаются решить конфликт зеркальным способом - становясь похожими на джентельменов, пытающихся уступить друг другу дорогу, при этом делая это одновременно, оставаясь таким образом друг напротив друга.
50. Отличие Thread::start и Thread::run?
thread.run() - вызывает run() of Runnable поля target, выполнение происходит в текущем потоке без создания нового.
thread.start() - создает новый поток, код run() of Runnable поля target выполняется в другом потоке, а не текущем.
3*. IllegalMonitorStateException. Когда вываливается и как избежать.
Вываливается, когда мы пытаем вызвать wait или notify/notifyAll на мониторе, которым мы не владеем в текещем потоке. Это будет с лучае, когда мы достали монитор откуда-то и пезцеременно пытаемся на нем дернуть эти методы, чтобы не сипалось исключение нам нужно обернуть такой вызов:
Реализует алгоритм work-stealing algorithm. Суть в том, что если мы можем разбить работу на кусочки, то мы это делаем, но только в том случае, если мы понимаем, что ее обьем это стоит. Для этого пользуются екзекьютором ForkJoinPool, в который ForkJoinTask. ForkJoinTask может быть либо RecursiveTask(возвращает какой-то итог), либо RecursiveAction(не возвращает, или устанавливает в разделенный обьект свою часть работы). И даже эти два класса тоже абстрактные, поэтому мы должны писать для них реализацию. Реализация должна реализовать защищенный метод protected void/Object compute(). Именно внутри должен быть произведен анализ на сколько оправданно разделяться, и если да, то на сколько частей и вызвать для этого invokeAll, если нет - то просто вызвать алгоритм в одном потоке.
В java 8 это активно юзается пакетом java.util.streams для паралельных исчислений(сортировки и так далее).
Все проекты связанные с многопоточностью я бы разделил на 3 класса: использующие многопоточность, основанные на многопоточности и те, которые и есть сама многопоточность. К первому классу("использующие") я бы отнес проекты, которые предполагают работу в многопоточной среде. Пример: есть класс BlaBlaBlaFactory, в документации необходимо указать, может ли эта фабрика использоваться одновременно несколькими потоками или нет. Если нет, то сделать ее потокобезопасной с помощью ReentrantLock. Ко второму классу ("основанные на") я бы отнес проекты, в которых использование нескольких потоков является одним из ключевых моментов. Пример: многопоточный кодек видео формата H.264. К третьему классу ("являются ей") я бы отнес проекты, в корне что-то меняющие в отношении потоков. Пример: написание runtime-среды для языка Scala (на Java) с реализацией легковесных потоков и своеобразной моделью памяти.
Так вот, дело в том, что разработчик зачастую указывает на свое знание java concurrency имея в виду первый уровень. Компания же может иметь в виду второй (написание высоконагруженного сервера, написание физического/AI ядра 3D-шутера, написание OLAP-системы — во всех случаях с требованием ОДИН запрос пользователя обрабатывать в НЕСКОЛЬКО потоков). Иногда разработчики не знают про существование уровней выше первого. Особенную лепту в это вносят книги вводного или обзорного характера.
Ниже я привожу ряд вопросов с гипотетического собеседования по java concurrency, относящиеся как к первому так и ко второму уровню. Надеюсь кому-то они покажут направление, в котором возможно развиваться.
Как и на всяком собеседовании, не предполагается, что интервьюируемый ответит на все вопросы. Но предполагается, что опрашиваемый по-крайней мере ознакомлен с тематикой. Кроме того определение того 1) что кандидат знает; 2) о чем слышал; 3) о чем не слышал; позволяет составить о нем более полное мнение.
1. Назовите различия между Collections.synchronizedMap(new HashMap()) и ConcurrentHashMap.
2. Что такое кооперативная многозадачность и она ли ли в Java. Если да, то какие преимущества. Если нет, то какая тогда в Java?
3. Что такое "зеленый потоки" и они ли ли в Java (в HotSpot JVM.6)?
4. Различия в интерфейсах Runnable и Callable.
5. Напишите минимальный неблокирующий стек (всего два метода — push() и pop()).
6. Напишите минимальный copy-on-write ArrayList (всего четыре метода — void add(int indx, int item), int get(int indx), void remove(int indx), int size()).
7. Различя между Thread.isInterrupded() и Thread.interrupded().
8. Что происходит при вызове Thread.interrupt()?
9. Некоторые из следующих методов deprecated а некоторые и не были никогда реализованы. Какие? Thread.interrupt(), Thread.stop(), Thread.yield(), Thread.suspend(), Thread.resume(), Thread.join(), Thread.start(), Thread.run(), Thread.sleep().
10. Что Вы знаете о асинхронных вызовов методов? Есть ли это в самом языке Java? Если есть, то как реализовано? Если нет, то как бы Вы реализовали?
11. Перечислите ВСЕ причины по которым может выскочить InterruptedException.
12. Что изменилось между JMM до Java 5 и NewJMM после Java 5?
13. В классе String все поля финальные. Можно ли убрать ключевое слово финал? Ведь сеттеров все равно нет — следовательно поля нельзя переустановить.
14. Что такое ordering, visibility, atomicity, happend-before, mutual exclusion. И показать на примерах volatile, AtomicInteger, synchronize{} — что из вышеперечисленного списка присутствует и при каких вариантах использования.
15. Назовите отличия synchronize{} и ReentrantLock.
16. Что из данных вызовов создает happend-before: Thread.sleep(), Thread.join(), Thread.yield(), Thread.start(), Thread.run(), Thread.isAlive(), Thread.getState()?
17. Перечислите известные Вам способы борьбы с priority inversion, назовите классы систем где они особенно опасны.
18. Перечислите известные Вам способы 1)избежать 2)побороть возникшие deadlock-и (представьте, что вы пишете ядро RDBMS).
18.1. Что такое livelock?
19. Расскажите о паттернах Reactor/Proactor?
20. Что такое "monitor"?
21. Что такое "private mutex"?
22. Что такое "priority inheritance"?
23. Что такое "backoff protocol (expotential backoff)"?
24. Что такое "task stealing"?
25. Что такое "ABA problem"?
26. Что такое "test-end-set"?
27. Что такое "test-and-test-end-set"?
28. Что такое "spin lock"?
29. Что такое "sequential consistency"?
30. Что такое "sense-reversing barrier"?
31. Что такое "safe publication"?
32. Что это за свойство — "reentrancy"?
33. Что такое "recursive parallelism"?
34. Что такое "iterative parallelism"?
35. Что это за вариант архитектуры "pipeline"?
36. Что такое "poison message"?
37. Что такое "mutual exclusion"? Примеры как добиться в Java.
38. Что такое "condition waiting"? Примеры как добиться в Java.
39. Преимущества SheduledThreadPool перед java.util.Timer.
40. Различия между java.util.concurrent.Atomic*.compareAndSwap() и java.util.concurrent.Atomic*.weakCompareAndSwap().
41. Что в SynchronousQueue уникально для BlockingQueue.
42. Что такое "рандеву"? При помощи каких классов в Java его можно организовать?
43. Что такое "false sharing". Может ли происходит в Java. Если есть, то приведите примеры и как бороться. Если нет, то как побороли разработчики JVM.
44. Thread.getState() возвращает экземпляр Thread.State. Какие возможны значения?
45. Напишите простейший ограниченный буфер для многих производителей/многих потребителей с использованием synchronize{}. С использованием ReentrantLock.
46. У ReentrantLock созданного с аргументом true все же один из методов захвата блоктровки — не fair. Какой? Как это обойти?
47. Приведите наиболее существенное отличие между CountDownLatch и Barrier.
48. Что Вы знаете о Erlang? Что в нем есть существенного связанного с многопоточностью такого, чего нет в Java?
49. Что Вы знаете о CSP? Что в нем есть существенного связанного с многопоточностью такого, чего нет в Java?
50. Отличие Thread.start() и Thread.run()?
http://books.google.com.ua/books?id=2pKCnc6-UAEC&printsec=frontcover&hl=ru#v=onepage&q&f=false
http://docs.oracle.com/javase/7/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html
http://www.sei.cmu.edu/reports/10tr015.pdf
+ Мои собственные вопросы:
1*. Thread.currentThread() -- какой именно тред возвращается, если у нас многоядернная/процессорная среда?
2*. CopyOnWriteArrayList специфика.
3*. IllegalMonitorStateException. Когда вываливается и как избежать.
4*. Fork/Join. Что это и о чем это?
А теперь ответы:
1. Назовите различия между Collections.synchronizedMap(new HashMap()) и ConcurrentHashMap.
2*. CopyOnWriteArrayList специфика.
Collections.synchronizedMap(new HashMap()) это декоратор вокруг потоконебезопасной хештаблицы, который делает каждый метод сингхронным, что дает псевдо потокозащищенность, но при этом абсолютно ограничивает масштабиремость такого решения. При этом для удачной вставки, удаления и прохода по элементам, нам нужно вводит дополнительную синхронизацию.
ConcurrentHashMap - потокобезопасная реализация, позволяющая мапе быть хорошо масштабируемой. Мы можем паралельно писать/читать, проходить по элементам, блокировка ставится либо на каждый бакет(контейнер-буфер в массиве, в котором хранятся обьекты с одинаковым хешом), либо на определенный диапазон(группу) бакетов. ConcurrentModificationException никогда не будет выкинуто во время прохода итератором, проход будет наблюдать состояния массива на момент создания итератора(удаления старых и добавление новых после создания итератора для этого цикла остануться незамеченными). Такая мапа подходит для массива кешей, куда часто пишут и часто читают из, но плохо подходит для массива требующего возращения своей длинны или проверки на пустоту.
ConcurrentReaderHashMap отличается от предыдущей реализацией тем, что одновременно для разных потоков умеет предоставлять только процедуру чтения елементов, но записать в один момент времени может только один поток.
CopyOnWriteArrayList потокобезопасный ArrayList. Он более полезен в паралельных исчислениях, где обход по массиву значительно превосходит операции вставки и удаления элементов(но они присутсвуют и нам бы не хотелось в такие моменты подвешивать все потоки на момент изменения колекции). На момент изменения колекции эта реализация создает копию массива и изменяет копию, при этом на момент этой процедуры остальные потоки получают ссылку на стараю копию для своих итераторов, которая по просту не меняется, когда процедура изменения заканчивается, реализация начинате отдавать итератором уже ссылку на новую копию с изменениями, а старые ссылки спокойно себе заканчивают работу со старой копией. На момент отспускания последнего итератора на старую копию, она удаляется из памяти.
CopyOnWriteArraySet под капотом использует просто CopyOnWriteArrayList.
2. Что такое кооперативная многозадачность и она ли ли в Java. Если да, то какие преимущества. Если нет, то какая тогда в Java?
КМ - это многозадачность, при которой каждый поток отвечает сам за передачу процессорных ресурсов другим потокам. Другой подход это упреждающая многозадачность(УМ) - когда планировщик выделяет определенные кванты времени для занимания процессора/ядра, так гарантируется, что не будет полного зависания системы, в случае ошибки в одном отдельном потоке, который не передает процессор другим; а также то, что самые низкоприоритетные процессы гарантированно получат доступ к процессорным ресурсам.
Если мы посмотрим на методы класса Thread, то в нем есть методы для добровольного передавания процессорного рессурса и отправки "сообщений" в другие потоки, но поскольку в JVM стоит маппинг на системные треды, то реально все-таки приложение будет работать упреждающе(хоть при этом поток сам тоже будет отдавать процессорное время, его также будут заставлять это делать), настоящей кооперативности можно добиться в случае явного включения зеленных потоков, но вот вопрос нужно ли это? - при этом если писать Java приложение в стиле кооперативности, то оно таким и будет, потому что прерванный машиной поток все равно держит высокоуровневые мониторы, и другой поток приложения будет проверять именно эти мониторы и тоже не будет выполняться).
3. Что такое "зеленые потоки" и они ли ли в Java (в HotSpot JVM.6)?
"Зеленые потоки" - это реализация многототочности на уровне виртуальной машины, многопоточность какбы симулируется, а по факту JVM выполняется в одном нативном потоке. Эту идею принесла JVM 1.1, но потом со следующий версий по умолчанию JVM мапит java threads на нативные ОС потоки в соответсвии 1:1. Нативные потоки в старых ОС была довольно дорогая штука в плане переключения между ними, сейчас, я полагаю, дела обстоят получше. Зеленые потоки дают некоторые преимущества: во первых между ними переключения очень быстрые, потому что они на уровне юзерспейса памяти и виртуальная машина делает "свою быструю магию"; и программы написанные под многопоточность, попадая на ОС без реализации многопоточности на уровне ядра, работали себе ни о чем не подозревая(сейчас в новых JVM, я думаю, можно также решить проблему на таких ОС, только нужно явно указывать флаги для переключения виртуалки в этот режим).
4. Различия в интерфейсах Runnable и Callable.
Runnable передается для вызова его метода run либо в Thread, либо Executor. При этом метода не возвращает значения.
Callable возвращает со своего единственного метода call значение типа, который определяется в реализацией обобщения. Этот интерфейс мы можем передать в расширение интерфейса Executor ExecutorService, который привносит методы, которые могут принимать Callable, а возвращать Future, который предоставляет возможность определить закончилось ли выполнение в другом потоке, и получить по готовности результат, или подвесить текущий поток и разблокировать, когда выполнение второго будет закончено, вернув значение.
package java.util.concurrent; public interface Future{ boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
7. Различя между Thread::isInterrupded и Thread::interrupded.
_thread.isInterrupded() это не статический метод, поэтому он применяется на переменной в которой у нас находится конкретный поток, этот метод позволяет протестировать был ли этот поток прерван. Используется для проверки другого потока(не себя).
Thread.interrupded() статический, и теcтирует поточный активный тред - был ли он прерван.
8. Что происходит при вызове Thread::interrupt()?
_thread.interrupt() - нестатический метод, поэтому его мы вызываем обычно для другого потока из текущего.
Внутри JVM есть флаг при каждом треде "интерапт статус", именно он устанавливается в истину, если вызывается этот метод. Статический Thread.interrupded() сбрасывает для себя этот флаг, а нестатический thread.isInterrupded() - нет. Это реализовано для переборочного подхода(проверка флага в цикле), без выброса исключения. По конвенции любой метод, который заверщает свою работу исключением InterruptedException обязуется сбрасывать флаг "интерапт статус".
while(someCyclicIncrementalProcess()) {
...
if (Thread.interrupted()) // Clears interrupted status!
throw new InterruptedException();
}
9. Некоторые из следующих методов deprecated, а некоторые и не были никогда реализованы. Какие? Thread.interrupt(), Thread.stop(), Thread.yield(), Thread.suspend(), Thread.resume(), Thread.join(), Thread.start(), Thread.run(), Thread.sleep().
_thread.interrupt() - это нестатический метод, применяется для генерирования исключения InterruptedException в потоке на который ссылается переменная, в идеале поток должен в своем run() ожидать такое исключение и подсыпать - передавая управление другим потокам.
_thread.stop() - deprecated, причина проста, метод подразумевает, что JVM scheduler(обвертка над ОС планировщиком или, если включена опция, планировщик зеленых потоков) попытается остановить поток как можно бытрее через исключение ThreadDeath, что подразумевает, что все блокировки будут отпущены на залоченных обьектах с их текущим состоянием на момент исключения(а значит - нестабильным), и вся "эта радость" станет доступна ждущим отпускания потокам.
Thread.yield() - статический, если дергатся статически, то подразумевается текущий поток. Это "уступить" означает, что поток говорит планировщику о своей готовности уступить CPU ресурсы для более "нужденных" процессов, если таких нет - то он готов продолжить свою работу немедленно. Обычно в бизнес приложениях это напрямую не используется, это для фреймворков высокоуровневых обьектов конкуренции.
_thread.suspend() - deprecated, причины те же, что и для стопа - это потенциально опасный механизм для дедлока. Он останавливает поток, пока не будет вызван метод резьюм(продолжить) - при этом подсыпание происходит мгновенно по решению с другого процесса, что означает в этот момент может быть занят лок, который должен быть свободен на случает попытки вызвать откуда-то resume, такой дедлок называется замароженный "процесс". Для безопасной реализации подобного подхода лучше пользоваться локом через _object.wait() throws InterruptedException и _object.notify()/notifyAll().
_thread.resume() - deprecated, все описано в suspend.
_thread.destroy() - deprecated, при этом он и не реализован.
10. Что Вы знаете о асинхронных вызовов методов? Есть ли это в самом языке Java? Если есть, то как реализовано? Если нет, то как бы Вы реализовали?
C jdk1.5 Для асинхронности используются Callable/Future. Для их работы нужно пользоваться ExecutorService, для получения екзекьюторов существует фабрика со статическими методами:
package java.util.concurrent;
public class Executors {
ExecutorService newFixedThreadPool(int nThreads);
ExecutorService newWorkStealingPool(int parallelism);
ExecutorService newWorkStealingPool();
ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
ExecutorService newSingleThreadExecutor();
ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory);
ExecutorService newCachedThreadPool();
ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
ScheduledExecutorService newSingleThreadScheduledExecutor();
ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory);
ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
ScheduledExecutorService newScheduledThreadPool;
ExecutorService unconfigurableExecutorService(ExecutorService executor);
ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor);
ThreadFactory defaultThreadFactory();
ThreadFactory privilegedThreadFactory();
<T>Callable<T> callable(Runnable task, T result);
Callable<Object> callable(Runnable task);
public static Callable<Object> callable(final PrivilegedAction action);
public static Callable<Object> callable(final PrivilegedExceptionAction action);
public static <T> Callable<T> privilegedCallable(Callable<T> callable);
public static <T> Callable<T> privilegedCallableUsingCurrentClassLoader(Callable<T> callable);
}
Приведенный список взят из jdk1.8 поэтому первые три метода точно добавленных в 1.8.
Есть такая задание, которое подходит неплохо для асинхронности. Когда у нас несколько задач, которые мы можем решить одновременно и потом нам нужно как-то сгрупировать их результаты и использовать эти результаты для итогового подсчета в главном потоке.
Логично, что мы в цикле запустим на выполнение количество Callable, соответсвующее количесву задач, и будем перебирать их Future в цикле на готовность если какая-то готовая, то тогда извлекать результат и продолжать перебор, пока все не закончатся, ну и потом завершить общий подсчет. И это в принципе не худший алгоритм решения. Гораздо хуже во время перебора не проверять на завершенность Future, а просто запрашивать результат, таким образом если у нас первая задача оказалась самой долговыполняющейся, то мы будем здать ее завершение, и только потом пойдем в цикле к следующим. Гораздо лучше равно Н(сколь задач) в цикле получить результаты, но от первого из завершенных потоков, таким образом мы ждем в главном потоке только тогда, когда ни один дочерний поток еще не завершил свои расчеты. Для такой полезной возможности jdk1.5 предлагает нам интерфейс CompletionService, который имеет метод take() он нам и вернет, первый фьючерс, который уже завершил свое исчесление, ожидая того времени, когда такой появится.
Есть такая задание, которое подходит неплохо для асинхронности. Когда у нас несколько задач, которые мы можем решить одновременно и потом нам нужно как-то сгрупировать их результаты и использовать эти результаты для итогового подсчета в главном потоке.
Логично, что мы в цикле запустим на выполнение количество Callable, соответсвующее количесву задач, и будем перебирать их Future в цикле на готовность если какая-то готовая, то тогда извлекать результат и продолжать перебор, пока все не закончатся, ну и потом завершить общий подсчет. И это в принципе не худший алгоритм решения. Гораздо хуже во время перебора не проверять на завершенность Future, а просто запрашивать результат, таким образом если у нас первая задача оказалась самой долговыполняющейся, то мы будем здать ее завершение, и только потом пойдем в цикле к следующим. Гораздо лучше равно Н(сколь задач) в цикле получить результаты, но от первого из завершенных потоков, таким образом мы ждем в главном потоке только тогда, когда ни один дочерний поток еще не завершил свои расчеты. Для такой полезной возможности jdk1.5 предлагает нам интерфейс CompletionService, который имеет метод take() он нам и вернет, первый фьючерс, который уже завершил свое исчесление, ожидая того времени, когда такой появится.
public interface CompletionService<V> {
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}
А вот пример реализации такой задачи на его реализации ExecutorCompletionService:
void solve(Executor e,
Collection<Callable<Result>> solvers)
throws InterruptedException {
CompletionService<Result> ecs
= new ExecutorCompletionService<Result>(e);
int n = solvers.size();
List<Future<Result>> futures
= new ArrayList<Future<Result>>(n);
Result result = null;
try {
for (Callable<Result> s : solvers)
futures.add(ecs.submit(s));
for (int i = 0; i < n; ++i) {
try {
Result r = ecs.take().get();
if (r != null) {
result = r;
break;
}
} catch (ExecutionException ignore) {}
}
}
finally {
for (Future<Result> f : futures)
f.cancel(true);
}
if (result != null)
use(result);
}
И еще один класс CompletableFuture появившийся в jdk1.8 расширяет Future и позволяет передавать колбеки, который будут выполнятся в потоке, который создает эту фичу, а вызываться по завершению потока фичи, реализуя интерфейса jdk1.8 CompletionStage.
11. Перечислите ВСЕ причины по которым может выскочить InterruptedException.
Нативные методы кидают из трек классов Thread, Object, Process.
1) Если поток А попытались присыпить из другого В, а третий С его прервал, то убаюкиватель В получить такое исключение в месте вызова sleep потока А.
2) Также если мы ждет от других потоков сообщение на разделенный монитор в поточном потоке, а этот поточный поток кто-то прервал, он будет пробужден этим исключением в месте ожидания монитора wait.
3) Если текущий поток А ждет завершение другого потока В, а его кто-то прервал, то он будет пробуждет исключением в месте вызова join потока В.
4) Ну и если мы запустили внешний процес, то мы можем подождать на его завершение, и если нас прервали мы опять таки пробуждаем в месте ожидания процесса waitFor.
Thread
.join() throws InterruptedException (void)
.join(long) throws InterruptedException (void)
.join(long, int) throws InterruptedException (void)
.sleep(long) throws InterruptedException (void)
.sleep(long, int) throws InterruptedException (void)
Object
.wait() throws InterruptedException (void)
.wait(long) throws InterruptedException (void)
.wait(long, int) throws InterruptedException (void)
Process (abstract class)
.waitFor() throws InterruptedException (int) - текущий поток переходит в спящее состояние до завершение подпроцесса представленного обьектом этого класса, по завершению получит код завершения. Или же будет из этого метода выкинуто исключение, если другой поток дернул у текущего метод interrupt().
.waitFor(long, Timeunit) throws InterruptedException (boolean) - вернется ложь, если подпроцесс не завершится до истечения указанного таймаута. Исключение вылитит при тех же условиях, что и в предыдущем методе.
Если мы коммуницируем между потоками с помощью синхронизированных методов на классах, а не вышеописанным инструментарием. То тогда будет полезно в таких сервисных классах, если операция длительная, периодически проверять на прервали ли нас, и тогда прерывать поточный поток, который захватил такой класс через его этот синхронизированный метод:
while(someFlagForComplitionAllPartitions()) {
somePartitionalJob();
if (Thread.interrupted()) // Clears interrupted status!
throw new InterruptedException();
}
Ну и еще в последнем фреймворке для конкуренции от JDK много новых более высокоуровневых классов для синхронизации умеют создавать и кидать такие исключения, но все они впринципе следуют правила предыдущего кастомного примера. Они проверяют не прервали ли текущий поток, работающий с этими классами и сообщают ему этим исключением. Это фьючерсы, очередя, пулы, таски, синхронизаторы и локи из пакетов:
java.util.concurrent
java.util.concurrent.locks
18.1. Что такое livelock?
Ситуация, которая возникает при неудачном просчете механизма разрешения дедлока. Потоки, которые попали в сцепку, одновременно пытаются решить конфликт зеркальным способом - становясь похожими на джентельменов, пытающихся уступить друг другу дорогу, при этом делая это одновременно, оставаясь таким образом друг напротив друга.
50. Отличие Thread::start и Thread::run?
thread.run() - вызывает run() of Runnable поля target, выполнение происходит в текущем потоке без создания нового.
thread.start() - создает новый поток, код run() of Runnable поля target выполняется в другом потоке, а не текущем.
3*. IllegalMonitorStateException. Когда вываливается и как избежать.
Вываливается, когда мы пытаем вызвать wait или notify/notifyAll на мониторе, которым мы не владеем в текещем потоке. Это будет с лучае, когда мы достали монитор откуда-то и пезцеременно пытаемся на нем дернуть эти методы, чтобы не сипалось исключение нам нужно обернуть такой вызов:
synchronized (monitor) {
monitor.wait(5000);
}
4*. Fork/Join. Что это и о чем это?Реализует алгоритм work-stealing algorithm. Суть в том, что если мы можем разбить работу на кусочки, то мы это делаем, но только в том случае, если мы понимаем, что ее обьем это стоит. Для этого пользуются екзекьютором ForkJoinPool, в который ForkJoinTask. ForkJoinTask может быть либо RecursiveTask(возвращает какой-то итог), либо RecursiveAction(не возвращает, или устанавливает в разделенный обьект свою часть работы). И даже эти два класса тоже абстрактные, поэтому мы должны писать для них реализацию. Реализация должна реализовать защищенный метод protected void/Object compute(). Именно внутри должен быть произведен анализ на сколько оправданно разделяться, и если да, то на сколько частей и вызвать для этого invokeAll, если нет - то просто вызвать алгоритм в одном потоке.
В java 8 это активно юзается пакетом java.util.streams для паралельных исчислений(сортировки и так далее).
Комментариев нет:
Отправить комментарий