Собеседование по Java — многопоточность (вопросы и ответы)
Вопросы и ответы для собеседования Java по теме — многопоточность.
К списку вопросов по всем темам
Вопросы
1. Дайте определение понятию “процесс”.
2. Дайте определение понятию “поток”.
3. Дайте определение понятию “синхронизация потоков”.
4. Как взаимодействуют программы, процессы и потоки?
5. В каких случаях целесообразно создавать несколько потоков?
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
7. Что вы знаете о главном потоке программы?
8. Какие есть способы создания и запуска потоков?
9. Какой метод запускает поток на выполнение?
10. Какой метод описывает действие потока во время выполнения?
11. Когда поток завершает свое выполнение?
12. Как синхронизировать метод?
13. Как принудительно остановить поток?
14. Дайте определение понятию “поток-демон”.
15. Как создать поток-демон?
16. Как получить текущий поток?
17. Дайте определение понятию “монитор”.
18. Как приостановить выполнение потока?
19. В каких состояниях может пребывать поток?
20. Что является монитором при вызове нестатического и статического метода?
21. Что является монитором при выполнении участка кода метода?
22. Какие методы позволяют синхронизировать выполнение потоков?
23. Какой метод переводит поток в режим ожидания?
24. Какова функциональность методов notify и notifyAll?
25. Что позволяет сделать метод join?
26. Каковы условия вызова метода wait/notify?
27. Дайте определение понятию “взаимная блокировка”.
28. Чем отличаются методы interrupt, interrupted, isInterrupted?
29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?
30. Модификаторы volatile и метод yield().
31. Пакет java.util.concurrent
32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?
33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?
34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?
35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?
36.Что такое ThreadGroup и зачем он нужен?
37.Что такое ThreadPool и зачем он нужен?
38.Что такое ThreadPoolExecutor и зачем он нужен?
39.Что такое «атомарные типы» в Java?
40.Зачем нужен класс ThreadLocal?
41.Что такое Executor?
42.Что такое ExecutorService?
43.Зачем нужен ScheduledExecutorService?
Ответы
1. Дайте определение понятию “процесс”.
Процесс — это совокупность кода и данных, разделяющих общее виртуальное адресное пространство. Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен (взаимодействие между процессами осуществляется с помощью специальных средств). Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.
Многопоточность в Java: http://habrahabr.ru/post/164487/
2. Дайте определение понятию “поток”.
Один поток («нить» или «трэд») – это одна единица исполнения кода. Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса.
Thinking in Java.Параллельное выполнение. http://wikijava.it-cache.net/index.php@title=Glava_17_Thinking_in_Java_4th_edition.html
3. Дайте определение понятию “синхронизация потоков”.
Синхронизация относится к многопоточности. Синхронизированный блок кода может быть выполнен только одним потоком одновременно.
Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация — это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванных непоследовательным доступом к общей памяти.
Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор.
Синхронизация достигается в Java использованием зарезервированного слова synchronized. Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized в переменных или атрибутах в определении класса.
Синхронизация потоков, блокировка объекта и блокировка класса info.javarush.ru: http://goo.gl/gW4ONp
4. Как взаимодействуют программы, процессы и потоки?
Чаще всего одна программа состоит из одного процесса, но бывают и исключения (например, браузер Chrome создает отдельный процесс для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга). В каждом процессе может быть создано множество потоков. Процессы разделены между собой (>программы), потоки в одном процессе могут взаимодействовать друг с другом (методы wait, notify, join и т.д.).
5. В каких случаях целесообразно создавать несколько потоков?
Многопоточные приложения применяются в случаях, когда можно разделить программу на несколько относительно независимых частей. В этом случае чтобы один код не ждал другой их помещают в различные потоки. В качестве примера можно привести программу с графическим интерфейсом — пока выполняются какие-либо длительные вычисления в одном потоке, интерфейс может быть доступен пользователю и не зависать, если он выполняется в другом потоке.
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
Если используются не синхронизированные данные, то может произойти ситуация, когда код работает уже с устаревшими данными. Например, в первом потоке идет изменение каких-либо полей, а в это время второй поток читает эти поля.
7. Что вы знаете о главном потоке программы?
Маленькие программы на Java обычно состоят из одной нити, называемой «главной нитью» (main thread). Но программы побольше часто запускают дополнительные нити, их еще называют «дочерними нитями». Главная нить выполняет метод main и завершается. Аналогом такого метода main, для дочерних нитей служит метод run интерфейса Runnable. Много потоков — много методов main (run()).
8. Какие есть способы создания и запуска потоков?
Существует несколько способов создания и запуска потоков.
С помощью класса, реализующего Runnable
- Создать объект класса Thread.
- Создать объект класса, реализующего интерфейс Runnable
- Вызвать у созданного объекта Thread метод start() (после этого запустится метод run() у переданного объекта, реализующего Runnable)
С помощью класса, расширяющего Thread
- Создать объект класса ClassName extends Thread.
- Переопределить run() в этом классе (смотрите пример ниже, где передается имя потока ‘Second’)
С помощью класса, реализующего java.util.concurrent.Callable
- Создать объект класса, реализующего интерфейс Callable
- Создать объект ExecutorService с указанием пула потоков.
- Создать объект Future. Запуск происходит через метод submit(); Сигнатура: <T> Future<T> submit(Callable<T> task)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public static void howToRunThreads() { ThreadClass threadClass = new ThreadClass("First"); threadClass.start(); //method ThreadClass.run() Thread thread = new Thread(new RunnableClass("Second")); Thread thread2 = new Thread(new RunnableClass("Third")); Thread thread3 = new Thread(new RunnableClass("Fourth")); thread.start(); //method RunnableClass.run() thread2.start(); //method RunnableClass.run() thread3.start(); //method RunnableClass.run() } public class RunnableClass implements Runnable { private String localName; public RunnableClass() { } public RunnableClass(String localName) { this.localName = localName; } @Override public void run() { System.out.println("run() " + localName + " running"); } public String getLocalName() {return localName;} public void setLocalName(String localName) {this.localName = localName;} } public class ThreadClass extends Thread { public ThreadClass() { } public ThreadClass(String name) { super(name); } public ThreadClass(Runnable target) { super(target); System.out.println(target + " will running"); } @Override public void run() { System.out.println("ThreadClass run() method " + "Thread name is: " + this.getName()); } } //Вывод ThreadClass run() method Thread name is: First run() Third running run() Fourth running run() Second running //обратите внимание, поменялись местами при выполнении. |
Пример с интерфейсом Callable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class CallableExample { public static class WordLengthCallable implements Callable { private String word; public WordLengthCallable(String word) { this.word = word; } public Integer call() { return Integer.valueOf(word.length()); } } public static void main(String args[]) throws Exception { ExecutorService pool = Executors.newFixedThreadPool(3); Set<Future<Integer>> set = new HashSet<Future<Integer>>(); for (String word: args) { Callable<Integer> callable = new WordLengthCallable(word); Future<Integer> future = pool.submit(callable); set.add(future); } int sum = 0; for (Future<Integer> future : set) { sum += future.get(); } System.out.printf("The sum of lengths is %s%n", sum); System.exit(sum); } } |
9. Какой метод запускает поток на выполнение?
Thread.start() запускает дочерний поток. Для интерфейса Callable запуск потока осуществляется с помощью метода submit().
10. Какой метод описывает действие потока во время выполнения?
Метод run() или метод call() для дочерних потоков. Метод main() для главного потока.
11. Когда поток завершает свое выполнение?
Поток закончит выполнение, когда завершится его метод run() или call(). Для главного потока это метод main().
12. Как синхронизировать метод?
Указать в сигнатуре модификатор synchronized или использовать конструкцию synchronized {} внутри метода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public void swap() { synchronized (this) { //someCode } } public synchronized void swap1EqualsSwap() { //someCode } public static synchronized void swap2() { //someCode } public static void swap3EqualsSwap2() { synchronized (JoinClass.class) { //someCode } } |
13. Как принудительно остановить поток?
В Java 8 нет метода, который бы принудительно останавливал поток. Никто не гарантирует, что нить можно остановить. Она может остановиться только сама. Java имеет встроенный механизм оповещения потока, который называется Interruption (прерывание, вмешательство).
Класс Thread содержит в себе скрытое булево поле, которое называется флагом прерывания. Установить этот флаг можно вызвав метод interrupt() потока. Проверить же, установлен ли этот флаг, можно двумя способами. Первый способ — вызвать метод bool isInterrupted() объекта потока, второй — вызвать статический метод bool Thread.interrupted(). Первый метод возвращает состояние флага прерывания и оставляет этот флаг нетронутым. Второй метод возвращает состояние флага и сбрасывает его. Заметьте что Thread.interrupted() — статический метод класса Thread, и его вызов возвращает значение флага прерывания того потока, из которого он был вызван. Поэтому этот метод вызывается только изнутри потока и позволяет потоку проверить своё состояние прерывания.
У методов, приостанавливающих выполнение потока, таких как sleep(), wait() и join() есть одна особенность — если во время их выполнения будет вызван метод interrupt() этого потока, они, не дожидаясь конца времени ожидания, сгенерируют исключение InterruptedException.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class JoinClass implements Runnable { @Override public void run() { for (int i=0; i<100; i++) { if(!Thread.interrupted()) { System.out.println(i); try { Thread.sleep(100); } catch (InterruptedException e) { System.out.println("InterruptedException!"); System.err.println("Exception msg: " + e.getMessage()); return; } } else { System.out.println("Interrupted!"); return; } } } } public class TestClass { public static void main(String[] args) { Thread thread = new Thread(new JoinClass()); thread.start(); try { Thread.sleep(1000L); // выключаем поток main, чтобы в JoinClass.run() что-то успело посчитаться } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } } //Вывод 0 1 ... 8 9 //счетчик был до 100 InterruptedException! Exception msg: sleep interrupted |
- В методе main() создаем объект класса JoinClass и запускаем его методом run(). Сначала проверяется не завершен ли уже этот поток, а затем каждые 100мс выводится значение счетчика.
- Главному методу приказывает подождать 1000мс, чтобы счетчик успел немножко посчитать.
- Вызываем interrupt метод у объекта класса JoinClass. После этого в цикле сразу ловится исключение и срабатывает return в блоке catch.
14. Дайте определение понятию “поток-демон”.
Потоками-демонами называются потоки, работающие в фоновом режиме для нашей программы.
В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main() уже завершился, но еще выполняются порожденные им потоки, система будет ждать их завершения.
Однако это правило не относится к особому виду потоков – демонам. Если завершился последний обычный поток процесса, и остались только потоки-демоны, то они будут принудительно завершены и выполнение процесса закончится. Чаще всего потоки-демоны используются для выполнения фоновых задач, обслуживающих процесс в течение его жизни.
15. Как создать поток-демон?
Объявить поток демоном достаточно просто — нужно перед запуском потока вызвать его метод setDaemon(true);
Проверить, является ли поток демоном, можно вызвав его метод boolean isDaemon();
1 2 3 |
Thread thread = new Thread(new DaemonClass()); thread.setDaemon(true); System.out.println(thread.isDaemon()); //true |
16. Как получить текущий поток?
Вызвать в коде статический метод Thread.currentThread(), который вернет текущий поток.
17. Дайте определение понятию “монитор”.
Несколько нитей могут мешать друг другу при обращении к одним и тем же данным. Для решения этой проблемы придуман мьютекс (он же монитор). Он имеет два состояния — объект занят и объект свободен. Монитор(мьютекс) — высокоуровневый механизм взаимодействия и синхронизации потоков, обеспечивающий доступ к неразделяемым ресурсам.
Когда какой-то нити нужен общий для всех нитей объект, она проверяет мьютекс, связанный с этим объектом. Если мьютекс свободен, то нить блокирует его (помечает как занятый) и начинает использование общего ресурса. После того, как она сделала свои дела, мьютекс разблокируется (помечается как свободен).
Если же нить хочет использовать объект, а мьютекс заблокирован, то нить засыпает в ожидании. Когда мьютекс, наконец, освободится занятой нитью, наша нить тут же заблокирует его и приступит к работе. Мьютекс встроен в класс Object и следовательно он есть у каждого объекта.
Когда одна нить заходит внутрь блока кода, помеченного словом synchronized, то Java-машина тут же блокирует мьютекс у объекта, который указан в круглых скобках после слова synchronized. Больше ни одна нить не сможет зайти в этот блок, пока наша нить его не покинет. Как только наша нить выйдет из блока, помеченного synchronized, то мьютекс тут же автоматически разблокируется и будет свободен для захвата другой нитью. Если же мьютекс был занят, то наша нить будет стоять на месте и ждать когда он освободится.
18. Как приостановить выполнение потока?
Thread.sleep() — статический метод класса Thread, который приостанавливает выполнение потока, в котором он был вызван. Во время выполнения метода sleep() система перестает выделять потоку процессорное время, распределяя его между другими потоками. Метод sleep() может выполняться либо заданное кол-во времени (миллисекунды или наносекунды) либо до тех пор пока он не будет остановлен прерыванием (в этом случае он сгенерирует исключение InterruptedException).
Возможен вопрос про старые методы suspend, stop и resume — они deprecated.
19. В каких состояниях может пребывать поток?
Поток может быть в следующем состоянии: созданный, запущенный, блокированный, остановленный, в режиме ожидания, в режиме ожидания по времени (NEW, RUNNABLE, BLOCKED, TERMINATED, WAITING, TIMED_WAITING).
20. Что является монитором при вызове нестатического и статического метода?
Для нестатического метода — текущий объект this. Для статического метода — объекта типа Class, соответствующий классу, в котором определен этот метод.
21. Что является монитором при выполнении участка кода метода?
Монитором является объект, указанный в блоке synchronized участка кода:
1 2 3 4 5 |
synchronized (synchedList) { ... synchedList.wait(); ... } |
22. Какие методы позволяют синхронизировать выполнение потоков?
К этим методам относятся notify(), notifyAll(), wait();
А как же всё-таки работает многопоточность? Часть I: синхронизация: http://habrahabr.ru/post/143237/
23. Какой метод переводит поток в режим ожидания?
Метод wait().
24. Какова функциональность методов notify и notifyAll?
Метод notify пробуждает один из потоков, который вызвал метод wait() у этого монитора. Метод notifyAll пробуждает все потоки. Очередность выполнения в этом случае будет определяться приоритетом потока.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
public class NotifyClass { private List synchedList; public NotifyClass() { // create a new synchronized list to be used synchedList = Collections.synchronizedList(new LinkedList()); } // method used to remove an element from the list public String removeElement() throws InterruptedException { synchronized (synchedList) { // while the list is empty, wait while (synchedList.isEmpty()) { System.out.println("List is empty..."); synchedList.wait(); System.out.println("Waiting..."); } String element = (String) synchedList.remove(0); return element; } } // method to add an element in the list public void addElement(String element) { System.out.println("Opening..."); synchronized (synchedList) { // add an element and notify all that an element exists synchedList.add(element); System.out.println("New Element:'" + element + "'"); synchedList.notifyAll(); System.out.println("notifyAll called!"); } System.out.println("Closing..."); } public static void main(String[] args) { final NotifyClass demo = new NotifyClass(); Runnable runA = new Runnable() { public void run() { try { String item = demo.removeElement(); System.out.println("" + item); } catch (InterruptedException ix) { System.out.println("Interrupted Exception!"); } catch (Exception x) { System.out.println("Exception thrown."); } } }; Runnable runB = new Runnable() { // run adds an element in the list and starts the loop public void run() { demo.addElement("Hello!"); } }; try { Thread threadA1 = new Thread(runA, "A"); threadA1.start(); Thread.sleep(500); Thread threadA2 = new Thread(runA, "B"); threadA2.start(); Thread.sleep(500); Thread threadB = new Thread(runB, "C"); threadB.start(); Thread.sleep(1000); threadA1.interrupt(); threadA2.interrupt(); } catch (InterruptedException x) { } } } //Вывод List is empty... List is empty... Opening... New Element:'Hello!' notifyAll called! Closing... Waiting... Waiting... List is empty... Hello! Interrupted Exception! |
25. Что позволяет сделать метод join?
Одна нить (поток) может вызвать метод join() у другой нити. В результате первый поток (который вызвал метод) приостанавливает свою работу и ждет окончания работы второго потока (у объекта которого был вызван метод join()).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class TestClass { public static void main(String[] args) { Thread threadExample = new Thread(new JoinClass()); threadExample.start(); try { threadExample.join(); //public static void TestClass.main() connect to threadExample and wait for it. } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("END: " + Calendar.getInstance().getTime()); } } public class JoinClass implements Runnable { @Override public void run() { System.out.println("JoinClass.run() " + Calendar.getInstance().getTime()); try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("afterSleep " + Calendar.getInstance().getTime()); } } //Вывод JoinClass.run() Tue Jan 19 00:00:37 MSK 2016 afterSleep Tue Jan 19 00:00:42 MSK 2016 END: Tue Jan 19 00:00:42 MSK 2016 |
После запуска метода main создается главный поток класса TestClass.
- Затем мы создаем тестовый поток threadExample и запускаем его.Заставляем поток протупить 5 секунд внутри метода JoinClass.run().
- После чего вызываем метод join() у второго потока. В этот момент главный поток подсоединяется к нашему второму потоку и ждет его завершения.
- Смотрим какое прошло время — 5 секунд. Т.е. главный поток ждал пока завершится threadExample до перехода к методу System.out.println(). В противном случае System.out.println(«END: «) выполнился сразу без ожидания пока отойдет threadExample.
26. Каковы условия вызова метода wait/notify?
Методы должны вызываться на объекте-мониторе только из синхронизированного кода. Поток, который вызывает эти методы должен владеть монитором, иначе будет выдано исключение java.lang.IllegalMonitorStateException.
27. Дайте определение понятию “взаимная блокировка”.
Deadlock, он же взаимная блокировка, явление при котором все потоки находятся в режиме ожидания. Чтобы уменьшить шанс появления deadlock’a не рекомендуется использовать методы wait() и notify().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
final Object lock1 = new Object(); final Object lock2 = new Object(); //Здесь будет дедлок void method01() { synchronized(lock1) { synchronized(lock2) { //doSomething() } } } void method1() { synchronized(lock2) { synchronized(lock1) { //doSomething() } } } |
Чтобы избежать дедлока можно использовать только один блок synchronized, отказаться от wait-notify или использовать такую конструкцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//Чтобы избежать дедлока, лучше использовать один lock. //Если нельзя использовать только один lock, то применяйте такую схему void method2() { if (identityHashCode(lock1)>=identityHashCode(lock2)) { synchronized(lock1) { synchronized(lock2) { //doSomething() } } } else { synchronized(lock2) { synchronized(lock1) { //doSomething() } } } } |
Java — Thread Deadlock: http://www.tutorialspoint.com/java/java_thread_deadlock.htm
Взаимная блокировка(deadlock) в Java и методы борьбы с ней: http://www.developersonthe.net/ru/posts/post_id/34-Vzaimnaja-blokirovkadeadlock-v-Java-i-metody-borby-s-nej/
28. Чем отличаются методы interrupt, interrupted, isInterrupted?
- Метод interrupt() — устанавливает флаг прерывания потока.
- Метод bool isInterrupted() объекта потока возвращает состояние флага прерывания и оставляет этот флаг нетронутым.
- Статический метод bool Thread.interrupted() — возвращает состояние флага и сбрасывает его.
29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?
Методы, требующие обработку этого исключения: wait, sleep, join. Исключение будет выброшено, если флаг interrupt у потока true.
30. Модификаторы volatile и метод yield().
Помещение модификатора volatile перед определением переменной заставляет принудительно всегда читать и писать значение только в обычную (медленную) память (а не кэшировать). Записывается как: private volatile boolean varName;
Статический метод Thread.yield() заставляет процессор переключиться на обработку других потоков системы. Метод может быть полезным, например, когда поток ожидает наступления какого-либо события и необходимо чтобы проверка его наступления происходила как можно чаще. В этом случае можно поместить проверку события и метод Thread.yield() в цикл:
1 2 3 4 5 |
//Ожидание поступления сообщения while(!msgQueue.hasMessages()) //Пока в очереди нет сообщений { Thread.yield(); //Передать управление другим потокам } |
31. Пакет java.util.concurrent
Обзор java.util.concurrent.*: http://habrahabr.ru/company/luxoft/blog/157273/
32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?
Да, иначе будет состояние гонки (race condition).
33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?
Да, можно создать новый экземпляр класса, пока его static synchronized метод выполняется. Мои рассуждения: статик методы классов создаются в единственном экземпляре во время загрузки класса класслоадером и принадлежат объекту Class.MyClass. Во время выполнения в потоке static synchronized метода захватывается блокировка именно этого объекта. Следовательно нам ничто не мешает создать новый экземпляр класса. Загрузчиков может быть несколько, соответственно, экземпляров Class<MyClass> — тоже несколько, по одному на загрузчик. Создавать новые инстанции ничего не мешает, т.к. это аналогично выделению машиной памяти под объект и вызова метода <init>, который не является synchronized.
34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?
Если в дочернем потоке упадет Exception, то метод run() аварийно завершится и исключение будет передано в главный поток. Далее в консоль будет выведен стектрейс, приведенный ниже.
Если исключение не обрабатывать, то нить (вызванная в методе run()) просто аварийно завершится. Восстановить работу нити после такого сценария нельзя, можно только создать нить заново.
1 2 3 4 5 |
Exception in thread "Thread-0" java.lang.NullPointerException Thread-0 at ru.javastudy.interview.multithreading.JoinClass.exceptionChecker(JoinClass.java:101) at ru.javastudy.interview.multithreading.JoinClass.run(JoinClass.java:24) at java.lang.Thread.run(Thread.java:745) |
35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?
Читайте выше о пакете concurrent. Реализация стандартными пакетами в статье от ibm:
Теория и практика Java: Пулы потоков и очередь действий: http://www.ibm.com/developerworks/ru/library/j-jtp0730/index.html
36.Что такое ThreadGroup и зачем он нужен?
ThreadGroup представляет собой набор нитей, которые так же могут содержать в себе другие группы потоков. Группа нитей образует дерево, в котором каждая другая группа нитей имеет родителя (кроме исходной). Поток имеет право доступа к данным из своей группы нитей, но не имеет такого доступа к другим группам или к родительской группе потоков.
Управление потоками, безопасность и ThreadGroup Java — http://src-code.net/upravlenie-potokami-bezopasnost-i-threadgroup-java/
37.Что такое ThreadPool и зачем он нужен?
Пулы потоков (нитей) представляют собой управляемую коллекцию потоков, которые доступны для выполнения различных задач. Пулы нитей, как правило, обеспечивают:
- Повышение производительности при выполнении большого количества задач в связи с сокращением накладных расходов на вызов каждой задачи.
- Является средством ограничивающим расход ресурсов при выполнении набора задач.
- Избавляют от необходимости управления жизненным циклом нитей.
38.Что такое ThreadPoolExecutor и зачем он нужен?
ThreadPoolExecutor — реализация ExecutorService. Он выполняет переданную задачу (Callable или Runnable), используя одну из внутренних доступных нитей из пула. Пул потоков содержит в себе ThreadPoolExecutor, который может содержать изменяющееся число нитей. Число нитей в пуле задается с помощью corePoolSize и maximumPoolSize.
http://tutorials.jenkov.com/java-util-concurrent/threadpoolexecutor.html
39.Что такое «атомарные типы» в Java?
Все атомарные классы переменных имеют базовый элемент Сравнение и назначение (compare-and-set) (аналогичный элементу Сравнение и замена), который реализуется при помощи самого быстрого собственного структурного компонента, который имеется в платформе (Сравнение и замена, Загрузить в связке, Сохранить при условии или в крайнем случае спин-блокировками). В пакет java.util.concurrent.atomic входят 9 видов атомарных переменных (AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean; формы для массивов атомарных целых чисел; длинные (long); ссылки; а также атомарные с пометкой Класс эталона (reference), которые атомарно обновляют две величины).
Классы атомарных переменных можно рассматривать как обобщение volatile переменных, если расширить понятие изменяемых переменных до переменных с поддержкой атомарных обновлений методом Сравнение и назначение. Чтение и запись атомарных переменных имеет такую же семантику памяти как доступ к чтению и записи изменяемых переменных.
Пример атомарной переменной AtomicLong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private volatile long value; public final long get() { return value; } public final long getAndAdd(long delta) { while (true) { long current = get(); long next = current + delta; if (compareAndSet(current, next)) return current; } } |
Переменные current и next — локальные, а следовательно у каждого потока свои экземпляры этих переменных. Следует обратить внимание лишь на разделяемое состояние, т.е. переменную value. Т.к. переменная value объявлена с модификатором volatile, то гарантируется выполнение отношения happens-before, что ведет к тому, что измененное значение этой переменной увидят все потоки.
Метод compareAndSet представляет из себя механизм оптимистичной блокировки и позволяет изменить значение value, только если оно равно ожидаемому значению (т.е. current).
Если же значение value было изменено в другом потоке, то оно не будет равно ожидаемому значению. Следовательно метод compareAndSet вернет значение false, что приведет к новой итерации цикла while в методе getAndAdd. Т.е. новое значение value будет перезачитано в переменную current, после чего будет произведено сложение и новая попытка записи получившегося значения (т.е. next).
http://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/
40.Зачем нужен класс ThreadLocal?
ThreadLocal предоставляет абстракцию над переменными локальными по отношению к потоку исполнения java.lang.Thread. ThreadLocal переменные отличаются от обычных переменных тем, что у каждого потока свой собственный, индивидуально инициализируемый экземпляр переменной, доступ к которой он получает через методы get() или set(). У каждого потока — т.е. экземпляра класса Thread — есть ассоциированная с ним таблица ThreadLocal-переменных. Ключами таблицы являются cсылки на объекты класса ThreadLocal, а значениями — ссылки на объекты, «захваченные» ThreadLocal-переменными.
http://samolisov.blogspot.ru/2011/04/threadlocal.html
http://articles.javatalks.ru/articles/17
41.Что такое Executor?
Executor — интерфейс, который может выполнять подтвержденные задачи. Интерфейс предоставляет возможность избежать вникания в механику выполнения задачи и деталей использования выполняемого потока. Executor обычно используется для явного создания нитей. Например так:
1 2 3 4 5 6 7 |
//Вместо Thread(new(RunnableTask())).start(): //Лучше использовать Executor executor = anExecutor; executor.execute(new RunnableTask1()); executor.execute(new RunnableTask2()); ... |
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html
42.Что такое ExecutorService?
ExecutorService исполняет асинхронный код в одном или нескольких потоках. Создание инстанса ExecutorService’а делается либо вручную через конкретные имплементации (ScheduledThreadPoolExecutor или ThreadPoolExecutor), но проще будет использовать фабрики класса Executors. Например, если надо создать пул с 2мя потоками, то делается это так:
1 |
ExecutorService service = Executors.newFixedThreadPool(2); |
Если требуется использовать кэширующий пул потоков, который создает потоки по мере необходимости, но переиспользует неактивные потоки (и подчищает потоки, которые были неактивные некоторое время), то это задается следующим образом:
1 |
ExecutorService service = Executors.newCachedThreadPool(); |
Если требуется запустить асинхронный код несколько раз, то это будет выполняться так:
1 2 3 4 5 6 7 8 |
ExecutorService service = Executors.newCachedThreadPool(); for(int i = 0; i < 10; i++) { service.submit(new Runnable() { public void run() { // snip... piece of code } }); } |
Метод submit также возвращает объект Future, который содержит информацию о статусе исполнения переданного Runnable или Callable (который может возвращать значение). Из него можно узнать выполнился ли переданный код успешно, или он еще выполняется. Вызов метода get на объекте Future возвратит значение, который возвращает Callable (или null, если используется Runnable). Метод имеет 2 checked-исключения: InterruptedException, который бросается, когда выполнение прервано через метод interrupt(), или ExecutionException если код в Runnable или Callable бросил RuntimeException, что решает проблему поддержки исключений между потоками.
Многопоточность в Java: ExecutorService — https://habrahabr.ru/post/116363/
43.Зачем нужен ScheduledExecutorService?
Иногда требуется выполнение кода асихронно и периодически или требуется выполнить код через некоторое время, тогда на помощь приходит ScheduledExecutorService. Он позволяет поставить код выполняться в одном или нескольких потоках и сконфигурировать интервал или время, на которое выполненение будет отложено. Интервалом может быть время между двумя последовательными запусками или время между окончанием одного выполнения и началом другого. Методы ScheduledExecutorService возвращают ScheduledFuture, который также содержит значение отсрочки для выполнения ScheduledFuture.
Если требуется отложить выполнение на 5 секунд, потребуется следующий код:
1 2 |
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); service.schedule(new Runnable() { ... }, 5, TimeUnit.SECONDS); |
Если требуется назначить выполнение каждую секунду:
1 2 |
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); service.scheduleAtFixedRate(new Runnable() { ... }, 0, 1, TimeUnit.SECONDS); |
И, наконец, если требуется назначить выполнение кода с промежутком 1 секунда между выполнениями:
1 2 |
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); service.scheduleWithFixedDelay(new Runnable() { ... }, 0, 1, TimeUnit.SECONDS); |
Многопоточность в Java: ExecutorService — https://habrahabr.ru/post/116363/
К списку вопросов по всем темам
8710 thoughts on “Собеседование по Java — многопоточность (вопросы и ответы)”
Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.
Как скоро вы собираетесь опубликовать ответы по многопоточности? очень ждем)
Через пару-тройку дней будет оформлена, а так часть ответов «как есть» будут с вечера.
Отлично))
с 34 не понятно, что имеется ввиду под «исключение улетит в главный поток». Насколько я понимаю главный поток так и будет штатно работать.
Просьба растолковать яснее.
Ответ №8.
public ThreadClass(Runnable target) {
super(target);
System.out.println(target + » will running»);
}
Видимо, тут нужен String в качестве аргумента, так как в коде Вы именно строку передаете в конструктор.
P.S. Спасибо за сайт. Очень здорово, что много интересного в одном месте.
Передана строка — это название класса-потока. Выводится по getName(). А конструкторы правильные указаны.
Строка 2: ThreadClass threadClass = new ThreadClass(«First»);
Определение класса ThreadClass (строки 33-47) не содержит конструктора, принимающего String.
public class ThreadClass extends Thread
Конструкторы не наследуются.
Спасибо Вам за проделанную работу!