Статьи

100 вопросов о многопоточности и параллельности Java: вопросы и ответы — ULTIMATE List (PDF Download)

В этой статье мы представляем исчерпывающую статью о многопоточности и вопросах и ответах по параллелизму Java.

ПРИМЕЧАНИЕ ДЛЯ РЕДАКЦИИ: параллелизм всегда является проблемой для разработчиков, и написание параллельных программ может быть чрезвычайно сложным.

Существует ряд вещей, которые могут взорваться, и сложность систем значительно возрастает, когда вводится параллелизм.

Тем не менее, способность писать надежные параллельные программы является отличным инструментом для разработчиков и может помочь в создании сложных приложений уровня предприятия.

В этой статье мы обсудим различные типы вопросов, которые могут быть использованы в программном интервью для оценки понимания кандидатом параллелизма и многопоточности.

Вопросы не только специфичны для Java, но и вращаются вокруг общих принципов программирования. Наслаждайтесь!

1. Процессы и потоки

1. Что мы понимаем под термином параллелизм?

Параллельность — это способность программы выполнять несколько вычислений одновременно. Это может быть достигнуто путем распределения вычислений по доступным ядрам ЦП машины или даже по разным машинам в одной сети.

Узнайте больше здесь:

Учебник по параллелизму Java 8

2. В чем разница между процессами и потоками?

Процесс — это среда выполнения, предоставляемая операционной системой, которая имеет собственный набор частных ресурсов (например, память, открытые файлы и т. Д.). Потоки, в отличие от процессов, живут внутри процесса и делятся своими ресурсами (памятью, открытыми файлами и т. Д.) С другими потоками процесса. Возможность совместного использования ресурсов между различными потоками делает поток более подходящим для задач, где производительность является существенным требованием.

3. Что такое процесс и поток в Java?

В Java процессы соответствуют работающей виртуальной машине Java (JVM), тогда как потоки живут в JVM и могут динамически создаваться и останавливаться приложением Java во время выполнения.

4. Что такое планировщик?

Планировщик — это реализация алгоритма планирования, который управляет доступом процессов и потоков к некоторому ограниченному ресурсу, такому как процессор или некоторый канал ввода-вывода. Целью большинства алгоритмов планирования является обеспечение некоторого рода распределения нагрузки для доступных процессов / потоков, который гарантирует, что каждый процесс / поток получает соответствующие временные рамки для доступа исключительно к запрошенному ресурсу.

5. Сколько потоков имеет хотя бы Java-программа?

Каждая Java-программа выполняется в основном потоке; следовательно, каждое Java-приложение имеет хотя бы один поток.

6. Как приложение Java может получить доступ к текущему потоку?

Доступ к текущему потоку можно получить, вызвав статический метод currentThread() из класса JDK java.lang.Thread :

1
2
3
4
5
6
7
8
public class MainThread {
    public static void main(String[] args) {
        long id = Thread.currentThread().getId();
        String name = Thread.currentThread().getName();
        ...
    }
}

7. Какими свойствами обладает каждый поток Java?

Каждый поток Java имеет следующие свойства:

  • идентификатор типа long, который является уникальным в JVM
  • имя типа String
  • приоритет типа int
  • состояние типа java.lang.Thread.State
  • группа потоков, к которой принадлежит поток

8. Каково назначение групп потоков?

Каждый поток принадлежит группе потоков. Класс JDK java.lang.ThreadGroup предоставляет несколько методов для обработки целой группы потоков. С помощью этих методов мы можем, например, прервать все потоки группы или установить их максимальный приоритет.

9. Какие состояния может иметь поток и каков смысл каждого состояния?

  • NEW: поток, который еще не начался, находится в этом состоянии.
  • RUNNABLE: поток, выполняющийся на виртуальной машине Java, находится в этом состоянии.
  • BLOCKED: поток, который заблокирован в ожидании блокировки монитора, находится в этом состоянии.
  • WAITING: поток, который неопределенно долго ожидает, пока другой поток выполнит определенное действие, находится в этом состоянии.
  • TIMED_WAITING: поток, ожидающий, пока другой поток выполнит действие в течение указанного времени ожидания, находится в этом состоянии.
  • TERMINATED: поток находится в этом состоянии.

10. Каковы преимущества многопоточного программирования?

Многопоточность вашего кода может помочь в следующих областях:

  • Улучшение отзывчивости приложений
  • Эффективное использование мультипроцессоров
  • Улучшение структуры программы
  • Использование меньшего количества системных ресурсов

11. С какими распространенными проблемами вы столкнулись в многопоточной среде?

  • Deadlock — два потока A и B, удерживайте lock_A и lock_B соответственно. Они оба хотят получить доступ к ресурсу R. Для безопасного доступа к R требуются и lock_A, и lock_B. Но поток A нуждается в lock_B, а поток B — в lock_A. Но оба они не готовы отказаться от замков, которые они держат. Следовательно, нет прогресса. Это тупик!
  • Условия гонки — Рассмотрим классический пример производителя-потребителя. Что если вы забудете заблокировать перед добавлением или удалением элемента из очереди? Представьте, что два потока A и B пытаются добавить элемент без блокировки. Поток A обращается к задней части очереди. Затем планировщик дает возможность запустить поток B, который успешно добавляет элемент и обновляет хвостовой указатель. Теперь указатель хвоста, прочитанный потоком A, устарел, но он думает, что это хвост, и добавляет элемент. Таким образом, пункт, добавленный B, потерян! Структура данных повреждена! Хуже того, это может также привести к утечке памяти во время очистки.
  • Гонка данных — представьте переменную флага, которую следует установить. Предположим, что вы установили замки, чтобы избежать условий гонки. Теперь разные потоки хотят устанавливать разные значения. Поскольку планировщик может планировать выполнение потока любым способом, вы не знаете, каково значение флага в конце.
  • Голодание — это проблема, вызванная планировщиком потоков. Некоторые потоки не имеют возможности запустить и завершить или не могут получить требуемые блокировки, потому что другим потокам предоставляется более высокий приоритет. Они «жаждут» циклов ЦП или других ресурсов.
  • Инверсия приоритетов. Представьте себе два потока A и B. A имеет более высокий приоритет, чем B, и, следовательно, получает больше циклов ЦП, чем B. Но при доступе к общему ресурсу B удерживает блокировку, которая также требуется для A, и возвращает. Теперь A не может ничего сделать без блокировки, и много циклов ЦП тратится впустую, потому что B не получает достаточно циклов, но имеет блокировку.

12. Как мы устанавливаем приоритет потока?

Приоритет потока устанавливается с помощью метода setPriority(int) . Чтобы установить приоритет для максимального значения, мы используем константу Thread.MAX_PRIORITY а для установки минимального значения мы используем константу Thread.MIN_PRIORITY поскольку эти значения могут различаться в разных реализациях JVM.

13. Что такое переключение контекста в многопоточности?

Чистая многозадачность не существует. Невозможно выполнить две умственно сложные задачи одновременно. Поэтому, когда мы работаем в многозадачном режиме, мы действительно постоянно переключаемся с одной задачи на другую. Вот что такое переключение контекста.

Термин происходит от информатики. Однако это так же применимо к умственным задачам, выполняемым людьми. В конце концов, человеческий разум во многом похож на процессор.

Подобно тому, как ЦП, на котором запущены многопоточные процессы, временно приостанавливает выполнение данного потока при выполнении другого, человеческий разум приостанавливает одну задачу, чтобы сместить фокус на другую.

14. Разница между зеленым потоком и собственным потоком в Java?

  • Зеленые потоки относятся к модели, в которой сама виртуальная машина Java создает, управляет и переключает контекст всех потоков Java в рамках одного процесса операционной системы. Библиотека потоков операционной системы не используется.
  • Под собственными потоками понимается объект, в котором виртуальная машина Java создает потоки Java и управляет ими с помощью библиотеки потоков операционной системы — с именем libthread в UnixWare — и каждый поток Java отображается в один поток библиотеки потоков.

15. Что мы понимаем под термином «состояние гонки»?

Условие гонки описывает созвездия, в которых результат некоторой многопоточной реализации зависит от точного поведения синхронизации участвующих потоков. В большинстве случаев нежелательно иметь такое поведение, поэтому термин условие гонки также означает, что ошибка из-за отсутствия синхронизации потоков приводит к разным результатам. Простой пример условия гонки — это увеличение целочисленной переменной двумя параллельными потоками. Поскольку операция состоит из более чем одной отдельной и атомарной операции, может случиться, что оба потока читают и увеличивают одно и то же значение. После этого одновременного увеличения величина целочисленной переменной увеличивается не на два, а только на один.

Узнайте больше здесь:

Пример java.util.concurrent.locks.Condition

16. Что вы должны учитывать при передаче экземпляров объекта из одного потока в другой?

При передаче объектов между потоками вам следует обратить внимание на то, что эти объекты не управляются двумя потоками одновременно. Примером может служить реализация Map , пары ключ / значение которой изменяются двумя параллельными потоками. Чтобы избежать проблем с одновременными изменениями, вы можете сделать объект неизменным.

17. Можно ли улучшить производительность приложения, используя многопоточность? Назовите несколько примеров.

Если у нас имеется более одного доступного ядра ЦП, производительность приложения можно повысить с помощью многопоточности, если возможно распараллелить вычисления на доступных ядрах ЦП. Примером может служить приложение, которое должно масштабировать все изображения, хранящиеся в структуре локального каталога. Вместо того, чтобы перебирать все изображения одно за другим, реализация производителя / потребителя может использовать один поток для сканирования структуры каталогов и группу рабочих потоков, которые выполняют фактическую операцию масштабирования. Другим примером может быть приложение, которое отображает некоторую веб-страницу. Вместо загрузки одной HTML-страницы за другой поток производителя может проанализировать первую HTML-страницу и выдать найденные ссылки в очередь. Рабочие потоки отслеживают очередь и загружают веб-страницы, найденные анализатором. Пока рабочие потоки ждут полной загрузки страницы, другие потоки могут использовать ЦП для анализа уже загруженных страниц и выдачи новых запросов.

18. Что мы понимаем под термином масштабируемость?

Масштабируемость означает способность программы повышать производительность, добавляя к ней дополнительные ресурсы.

19. Можно ли рассчитать теоретическую максимальную скорость для приложения, используя несколько процессоров?

Закон Амдала предоставляет формулу для расчета теоретической максимальной скорости, предоставляя приложение нескольким процессорам. Теоретическое ускорение вычисляется как S(n) = 1 / (B + (1-B)/n) где n обозначает количество процессоров, а B — часть программы, которая не может быть выполнена параллельно. Когда n сходится к бесконечности, член (1-B)/n сходится к нулю. Следовательно, формула может быть уменьшена в этом частном случае до 1/B Как мы видим, теоретическое максимальное ускорение ведет себя взаимно с дробью, которая должна выполняться последовательно. Это означает, что чем ниже эта доля, тем большее теоретическое ускорение может быть достигнуто.

20. Приведите пример, почему улучшения производительности для однопоточных приложений могут привести к снижению производительности многопоточных приложений.

Ярким примером такой оптимизации является реализация List , в которой количество элементов хранится в виде отдельной переменной. Это повышает производительность для однопоточных приложений, поскольку операция size() не должна повторяться по всем элементам, но может возвращать текущее количество элементов напрямую. В многопоточном приложении дополнительный счетчик должен защищаться блокировкой, поскольку несколько параллельных потоков могут вставлять элементы в список. Эта дополнительная блокировка может стоить производительности, когда в списке больше обновлений, чем вызовов операции size() .

2.Thread объектов

2.1 Определение и запуск потока

21. Как создается нить в Java?

В принципе, существует два способа создания потока в Java.
Первый — написать класс, который расширяет класс JDK java.lang.Thread и вызвать его метод start() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println("Executing thread "+Thread.currentThread().getName());
    }
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread("myThread");
        myThread.start();
    }
}

Второй способ — реализовать интерфейс java.lang.Runnable и передать эту реализацию в качестве параметра конструктору java.lang.Thread :

01
02
03
04
05
06
07
08
09
10
11
public class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Executing thread "+Thread.currentThread().getName());
    }
    public static void main(String[] args) throws InterruptedException {
        Thread myThread = new Thread(new MyRunnable(), "myRunnable");
        myThread.start();
    }
}

22. Почему нельзя остановить поток, вызвав его метод stop() ?

Не следует останавливать поток с помощью устаревших методов stop() из java.lang.Thread , поскольку вызов этого метода заставляет поток разблокировать все полученные им мониторы. Если какой-либо объект, защищенный одной из снятых блокировок, находился в несогласованном состоянии, это состояние становится видимым для всех других потоков. Это может вызвать произвольное поведение, когда другие потоки работают с этим несовместимым объектом.

23. Можно ли запустить поток дважды?

Нет, после запуска потока путем вызова его метода start() , второй вызов start() вызовет IllegalThreadStateException .

24. Что выводит следующий код?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class MultiThreading {
    private static class MyThread extends Thread {
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread("myThread");
        myThread.run();
    }
}

Приведенный выше код производит вывод «main», а не «myThread». Как видно из второй строки метода main() , мы по ошибке вызываем метод run() вместо start() . Следовательно, новый поток не запускается, но метод run() выполняется в основном потоке.

25. Что такое поток демона?

Поток демона — это поток, состояние выполнения которого не оценивается, когда JVM решает, останавливаться или нет. JVM останавливается, когда все пользовательские потоки (в отличие от потоков демона) завершаются. Следовательно, потоки демона могут быть использованы, например, для реализации функциональности мониторинга, поскольку JVM останавливает поток, как только все пользовательские потоки останавливаются:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Example {
    private static class MyDaemonThread extends Thread {
        public MyDaemonThread() {
            setDaemon(true);
        }
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyDaemonThread();
        thread.start();
    }
}

Приведенный выше пример приложения завершается, хотя поток демона все еще работает в своем бесконечном цикле while.

26. Что такое дамп Java Thread?

Дамп потока Java — это способ выяснить, что каждый поток в JVM делает в определенный момент времени.

Это особенно полезно, если приложение Java иногда зависает при работе под нагрузкой, так как анализ дампа покажет, где застряли потоки.

Вы можете создать дамп потока в Unix / Linux, запустив kill -QUIT <pid>, а в Windows — нажав Ctl + Break.

27. Можно ли преобразовать обычный пользовательский поток в поток демона после его запуска?

Пользовательский поток не может быть преобразован в поток демона после его запуска. Вызов метода thread.setDaemon(true) для уже запущенного экземпляра потока вызывает IllegalThreadStateException .

28. Что мы понимаем под занятым ожиданием?

Оживленное ожидание означает реализации, которые ожидают события, выполняя некоторые активные вычисления, которые позволяют потоку / процессу занимать процессор, хотя он может быть удален из него планировщиком. Примером занятого ожидания может быть время ожидания в цикле, который снова и снова определяет текущее время, пока не будет достигнут определенный момент времени:

01
02
03
04
05
06
07
08
09
10
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        long millisToStop = System.currentTimeMillis() + 5000;
        long currentTimeMillis = System.currentTimeMillis();
        while (millisToStop > currentTimeMillis) {
            currentTimeMillis = System.currentTimeMillis();
        }
    }
});

29. Чем отличаются методы wait () и sleep () в Java?

Подождите():

  • Метод wait () снимает блокировку.
  • wait () — это метод класса Object.
  • wait () — нестатический метод — public final void wait () генерирует InterruptedException {//…}
  • wait () должен быть уведомлен методами notify () или notifyAll ().
  • Метод wait () должен вызываться из цикла, чтобы справиться с ложной тревогой.
  • Метод wait () должен вызываться из синхронизированного контекста (т. е. синхронизированного метода или блока), в противном случае он вызовет исключение IllegalMonitorStateException

спать():

  • Метод sleep () не снимает блокировку.
  • sleep () — это метод класса java.lang.
  • sleep () — это статический метод — public static void sleep (long millis, int nanos) выдает InterruptedException {//…}
  • по истечении указанного времени сон () завершен.
  • sleep () лучше не вызывать из цикла (т.е. см. код ниже).
  • sleep () может вызываться из любого места. нет особых требований.

30. Что происходит, когда необработанное исключение покидает метод run() ?

Может случиться, что непроверенное исключение выходит из метода run() . В этом случае поток останавливается виртуальной машиной Java. Можно перехватить это исключение, зарегистрировав экземпляр, который реализует интерфейс UncaughtExceptionHandler в качестве обработчика исключений.

Это можно сделать либо путем вызова статического метода Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler) , который указывает JVM использовать предоставленный обработчик в случае, если в самом потоке не зарегистрирован конкретный обработчик, либо путем вызова setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler) сам экземпляр потока.

31. В чем разница между двумя интерфейсами Runnable и

Callable?

Интерфейс Runnable определяет метод run() без какого-либо возвращаемого значения, тогда как интерфейс Callable позволяет call() метода call() возвращать значение и генерировать исключение.

32. Что такое крюк отключения?

Хук отключения — это поток, который выполняется, когда JVM завершает работу. Его можно зарегистрировать, вызвав addShutdownHook(Runnable) в экземпляре Runtime:

1
2
3
4
5
6
Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
    }
});

2.2 Приостановка исполнения со сном

33. Как мы можем предотвратить занятое ожидание?

Один из способов предотвращения ожидания занятости — перевести текущий поток в спящий режим на заданное время. Это можно сделать, вызвав метод java.lang.Thread.sleep(long) , передав число спящих в миллисекундах в качестве аргумента.

34. Можем ли мы использовать Thread.sleep() для обработки в реальном времени?

Количество миллисекунд, передаваемых на вызов Thread.sleep(long) является только указанием для планировщика, как долго текущий поток не нужно выполнять. Может случиться так, что планировщик позволяет потоку исполниться снова несколькими миллисекундами раньше или позже, в зависимости от фактической реализации. Следовательно, вызов Thread.sleep() не должен использоваться для обработки в реальном времени.

35. Что делает метод Thread.yield() ?

Вызов статического метода Thread.yield() дает планировщику подсказку, что текущий поток готов освободить процессор. Планировщик может игнорировать эту подсказку. Поскольку не определено, какой поток получит процессор после вызова Thread.yield() , может даже случиться, что текущий поток станет «следующим» потоком, который будет выполнен.

36. Какие варианты использования для класса java.util.concurrent.Future ?

Экземпляры класса java.util.concurrent.Future используются для представления результатов асинхронных вычислений, результаты которых не доступны сразу. Следовательно, класс предоставляет методы для проверки завершения асинхронных вычислений, отмены задачи и получения фактического результата. Последнее можно сделать с помощью двух методов get() . Первый метод get() не принимает параметров и блокируется до тех пор, пока результат не станет доступен, тогда как второй метод get() принимает параметр тайм-аута, который позволяет возвращать вызов метода, если результат не становится доступным в течение заданного периода времени.

Узнайте больше здесь:

Пример java.util.concurrent.FutureTask

Пример Java CompletionStage и CompletableFuture

37. Как мы можем остановить поток в Java?

Чтобы остановить поток, можно использовать энергозависимую ссылку, указывающую на текущий поток, которая может быть установлена ​​равной нулю другими потоками, чтобы указать, что текущий поток должен остановить свое выполнение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static class MyStopThread extends Thread {
    private volatile Thread stopIndicator;
    public void start() {
        stopIndicator = new Thread(this);
        stopIndicator.start();
    }
    public void stopThread() {
        stopIndicator = null;
    }
    @Override
    public void run() {
        Thread thisThread = Thread.currentThread();
        while(thisThread == stopIndicator) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}

2.3 Прерывания

38. Как можно разбудить поток, который был усыплен перед использованием Thread.sleep() ?

Метод interrupt() java.lang.Thread прерывает спящий поток. Прерванный поток, который был переведен в режим Thread.sleep() вызовом Thread.sleep() InterruptedException :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class InterruptExample implements Runnable {
    public void run() {
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            System.out.println("["+Thread.currentThread().getName()+"] Interrupted by exception!");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread myThread = new Thread(new InterruptExample(), "myThread");
        myThread.start();
        System.out.println("["+Thread.currentThread().getName()+"] Sleeping in main thread for 5s...");
        Thread.sleep(5000);
        System.out.println("["+Thread.currentThread().getName()+"] Interrupting myThread");
        myThread.interrupt();
    }
}

39. Как может запрос потока, если он был прерван?

Если поток не находится в методе, подобном Thread.sleep() который Thread.sleep() InterruptedException , поток может запросить прерывание, вызвав статический метод Thread.interrupted() или метод isInterrupted() который он унаследовал. от java.lang.Thread.

40. Как обрабатывать InterruptedException ?

Такие методы, как sleep() и join() генерируют InterruptedException чтобы сообщить вызывающей стороне, что другой поток прервал этот поток. В большинстве случаев это делается для того, чтобы сообщить текущему потоку прекратить текущие вычисления и неожиданно завершить их. Следовательно, игнорирование исключения путем его перехвата и регистрации только на консоли или в каком-либо файле журнала часто не является подходящим способом обработки такого рода исключения. Проблема с этим исключением состоит в том, что метод run() интерфейса Runnable не позволяет, чтобы run() генерировала какие-либо исключения. Так что просто сбросить это не поможет. Это означает, что реализация run() должна сама обрабатывать это проверенное исключение, и это часто приводит к тому, что оно перехватывается и игнорируется.

2.4 Присоединения

41. После запуска дочернего потока, как нам ждать в родительском потоке завершения дочернего потока?

Ожидание завершения потока выполняется путем вызова метода join() для переменной экземпляра потока:

1
2
3
4
5
6
7
8
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
    }
});
thread.start();
thread.join();

42. Каков вывод следующей программы?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyThreads {
    private static class MyDaemonThread extends Thread {
        public MyDaemonThread() {
            setDaemon(true);
        }
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyDaemonThread();
        thread.start();
        thread.join();
        System.out.println(thread.isAlive());
    }
}

Вывод вышеуказанного кода — «ложь». Хотя экземпляр MyDaemonThread является потоком демона, вызов метода join() заставляет главный поток ждать, пока не завершится выполнение потока демона. Следовательно, вызов isAlive() для экземпляра потока показывает, что поток демона больше не работает.

3. Синхронизация

43. Различия между синхронизированным методом и синхронизированным блоком?

  • синхронизированный блок уменьшает область блокировки, но область действия синхронизированного метода — это целый метод.
  • синхронизированный блок имеет лучшую производительность, так как блокируется только критическая секция, но синхронизированный метод имеет низкую производительность, чем блок.
  • синхронизированный блок обеспечивает детальный контроль над блокировкой, но синхронизированный метод блокирует либо текущий объект, представленный этим, либо блокировку уровня класса.
  • Синхронизированный блок может генерировать исключение NullPointerException, но синхронизированный метод не генерирует.
  • синхронизированный блок: синхронизированный (это) {}
  • синхронизированный метод: public синхронизированный void fun () {}

44. Что такое ключевое слово volatile в Java и чем оно отличается от синхронизированного метода в Java?

Использование volatile заставляет поток читать и записывать переменные непосредственно из оперативной памяти. Поэтому, когда многие потоки используют одну и ту же переменную переменную, все они видят последнюю версию, которая присутствует в оперативной памяти, а не возможную старую копию в кэше. Когда поток входит в синхронизированный блок, он должен получить контроль над переменной монитора. Все остальные потоки ожидают выхода первого потока из синхронизированного блока. Чтобы все потоки могли видеть одинаковые изменения, все переменные, используемые в синхронизированном блоке, считываются и записываются непосредственно из памяти RAM, а не из копии кэша.

45. Для каких целей используется ключевое слово synchronized?

Когда вам нужно реализовать монопольный доступ к ресурсу, например, к некоторому статическому значению или некоторой ссылке на файл, код, работающий с монопольным ресурсом, может быть заключен в синхронизированный блок:

1
2
3
synchronized (SynchronizedCounter.class) {
    counter++;
}

46. ​​Что такое семафор?

Семафор — это структура данных, которая поддерживает набор разрешений, которые должны быть получены конкурирующими потоками. Таким образом, семафоры могут использоваться для управления количеством потоков, одновременно обращающихся к критическому разделу или ресурсу. Следовательно, конструктор java.util.concurrent.Semaphore принимает в качестве первого параметра количество разрешений, с которыми конкурируют потоки. Каждый вызов его методов acquire() пытается получить одно из доступных разрешений. Метод acquire() без блоков параметров, пока не будет доступно следующее разрешение. Позже, когда поток завершил свою работу с критическим ресурсом, он может освободить разрешение, вызвав метод release() для экземпляра Semaphore.

Узнайте больше здесь:

Пример семафоров, ограничивающих соединения URL

Пример java.util.concurrent.Semaphore

47. Что такое CountDownLatch ?

Класс SDK CountDownLatch предоставляет вспомогательное средство синхронизации, которое можно использовать для реализации сценариев, в которых потокам приходится ждать, пока некоторые другие потоки не достигнут того же состояния, чтобы все потоки могли запускаться. Это делается путем предоставления синхронизированного счетчика, который уменьшается до тех пор, пока не достигнет нулевого значения. Достигнув нуля, экземпляр CountDownLatch позволяет всем потокам продолжаться. Это можно использовать для запуска всех потоков в определенный момент времени с помощью значения 1 для счетчика или для ожидания завершения нескольких потоков. В последнем случае счетчик инициализируется числом потоков, и каждый завершивший работу поток считает защелку на единицу.

48. В чем разница между CountDownLatch и CyclicBarrier ?

Оба класса SDK поддерживают внутренний счетчик, который уменьшается различными потоками. Потоки ждут, пока внутренний счетчик не достигнет нулевого значения, и продолжат с этого момента. Но в отличие от CountDownLatch класс CyclicBarrier сбрасывает внутреннее значение обратно к начальному значению, как только значение достигает нуля. Поскольку имя указывает, что экземпляры CyclicBarrier могут поэтому использоваться для реализации случаев использования, когда потокам приходится ждать друг друга снова и снова.

Узнайте больше здесь:

Пример Java CountDownLatch

Пример java.util.concurrent.Phaser

Пример Java.util.concurrent.CyclicBarrier

Пример CountDownLatch более общего механизма ожидания / уведомления

3.1 Собственные блокировки и синхронизация

49. Какую внутреннюю блокировку получает синхронизированный метод?

Синхронизированный метод получает встроенную блокировку для объекта этого метода и освобождает ее, когда метод возвращается. Даже если метод выдает исключение, внутренняя блокировка освобождается. Следовательно, синхронизированный метод равен следующему коду:

1
2
3
4
5
public void method() {
    synchronized(this) {
        ...
    }
}

50. Если два потока одновременно вызывают синхронизированный метод для разных экземпляров объекта, может ли один из этих потоков блокировать?

Оба метода блокируют один и тот же монитор. Следовательно, вы не можете одновременно выполнять их на одном и том же объекте из разных потоков (один из двух методов будет блокироваться, пока другой не будет завершен).

51. Может ли конструктор быть синхронизирован?

Нет, конструктор не может быть синхронизирован. Причина, по которой это приводит к синтаксической ошибке, заключается в том, что только конструирующий поток должен иметь доступ к создаваемому объекту.

52. Каковы преимущества Lock по сравнению с синхронизацией?

Преимущества замка:

  • можно сделать их честными
  • можно сделать поток реагирующим на прерывание во время ожидания объекта Lock.
  • можно попытаться получить блокировку, но вернуться сразу или по истечении времени ожидания, если блокировка не может быть получена
  • можно приобретать и снимать замки в разных областях и в разных заказах

53. Можно ли использовать примитивные значения для внутренних замков?

Нет, примитивные значения не могут быть использованы для внутренних замков.

54. Являются ли внутренние замки реентерабельными?

Да, внутренние замки могут быть доступны для одного и того же потока снова и снова. В противном случае код, который получает блокировку, должен обратить внимание на то, что он не пытается случайно получить блокировку, которую он уже получил.

55. Что мы понимаем под честными замками?

Добросовестная блокировка учитывает время ожидания потоков при выборе следующего потока, который передает барьер какому-либо исключительному ресурсу. Пример реализации справедливой блокировки представлен Java SDK: java.util.concurrent.locks.ReentrantLock . Если используется конструктор с булевым флагом, установленным в true, ReentrantLock предоставляет доступ к самому длинному ожидающему потоку.

56. Какой метод снижения конкуренции за блокировку используется классом SDK ReadWriteLock?

Класс ReadWriteLock использует тот факт, что параллельным потокам не нужно устанавливать блокировку, когда они хотят прочитать значение, когда никакой другой поток не пытается обновить значение. Это реализуется парой блокировок, одна для операций только для чтения и одна для операций записи. Хотя блокировка только для чтения может быть получена более чем одним потоком, реализация гарантирует, что все операции чтения увидят обновленное значение после снятия блокировки записи.

Узнайте больше здесь:

Пример Java ReentrantLock

Пример Java ReadWriteLock

Пример Java ReentrantReadWriteLock

Reentrant Lock пример бегуна задач

Reentrant ReadWriteLock пример калькулятора стоимости

3.2 Атомный доступ

57. Что мы понимаем под атомной операцией?

Атомарная операция — это операция, которая либо выполняется полностью, либо не выполняется вообще.

58. Является ли утверждение c ++ атомарным?

Нет, приращение целочисленной переменной состоит из более чем одной операции. Сначала мы должны загрузить текущее значение c, увеличить его, а затем, наконец, сохранить новое значение обратно. Текущий поток, выполняющий это приращение, может быть прерван между этими тремя шагами, следовательно, эта операция не является атомарной.

59. Какие операции являются атомарными в Java?

Язык Java предоставляет некоторые базовые операции, которые являются атомарными и поэтому могут использоваться, чтобы гарантировать, что параллельные потоки всегда видят одно и то же значение:

  • Операции чтения и записи для ссылок на переменные и примитивные переменные (кроме long и double)
  • Операции чтения и записи для всех переменных, объявленных как volatile

4. Живость

4.1 тупик

60. Что мы понимаем под тупиком?

Взаимная блокировка — это ситуация, в которой два (или более) потока каждый ожидают в другом потоке, чтобы освободить заблокированный ресурс, а сам поток заблокировал ресурс, которого ожидает другой поток:

  • Поток 1: блокирует ресурс A, ожидает ресурс B
  • Поток 2: блокирует ресурс B, ожидает ресурс A

61. Каковы требования к тупиковой ситуации?

В целом можно определить следующие требования для тупика:

  • Взаимное исключение. Существует ресурс, к которому может обращаться только один поток в любой момент времени.
  • Удержание ресурса: заблокировав один ресурс, поток пытается получить другую блокировку для другого исключительного ресурса.
  • Нет выгрузки: не существует механизма, который освобождает ресурс, если один поток удерживает блокировку в течение определенного периода времени.
  • Круговое ожидание: во время выполнения возникает созвездие, в котором два (или более) потока каждый ожидают в другом потоке, чтобы освободить заблокированный ресурс.

62. Можно ли вообще предотвратить тупики?

Для предотвращения взаимных блокировок одно (или более) из требований для взаимоблокировки должно быть устранено:

  • Взаимное исключение: в некоторых ситуациях можно предотвратить взаимное исключение, используя оптимистическую блокировку.
  • Удержание ресурса. Поток может освободить все свои эксклюзивные блокировки, когда ему не удается получить все эксклюзивные блокировки.
  • Нет преимуществ: использование тайм-аута для эксклюзивной блокировки освобождает блокировку по истечении заданного промежутка времени.
  • Циклическое ожидание: когда все эксклюзивные блокировки получены всеми потоками в одной и той же последовательности, циклическое ожидание не происходит.

63. Возможно ли реализовать обнаружение тупиковой ситуации?

Когда все исключительные блокировки отслеживаются и моделируются как ориентированный граф, система обнаружения взаимоблокировок может искать два потока, каждый из которых ожидает в другом потоке, чтобы освободить заблокированный ресурс. Затем ожидающие потоки могут быть вынуждены с помощью какого-то исключения снять блокировку, которую ожидает другой поток.

4.2 Голодание и Livelock

64. Что такое живой замок?

Живая блокировка — это ситуация, в которой два или более потоков блокируют друг друга, реагируя на действие, вызванное другим потоком. В отличие от ситуации взаимоблокировки, когда два или более потоков ждут в одном определенном состоянии, потоки, участвующие в livelock, изменяют свое состояние таким образом, чтобы предотвратить прогресс в их обычной работе. Примером может служить ситуация, когда два потока пытаются получить две блокировки, но снимают блокировку, которую они получили, когда они не могут получить вторую блокировку. Теперь может случиться, что оба потока одновременно пытаются получить первый поток. Как только один поток завершается успешно, второй поток может успешно получить вторую блокировку. Теперь оба потока поддерживают две разные блокировки, но, поскольку обе хотят иметь обе блокировки, они снимают блокировку и пытаются снова с самого начала. Такая ситуация теперь может повторяться снова и снова.

65. Что мы понимаем под голодным потоком?

Потоки с более низким приоритетом получают меньше времени для выполнения, чем потоки с более высоким приоритетом. Когда потоки с более низким приоритетом выполняют длительные вычисления, может случиться, что эти потоки не получат достаточно времени, чтобы закончить свои вычисления как раз вовремя. Похоже, они «умирают», поскольку потоки с более высоким приоритетом крадут у них время вычислений.

66. Может ли синхронизированный блок вызвать истощение потока?

Порядок, в котором потоки могут войти в синхронизированный блок, не определен.Таким образом, теоретически может случиться так, что если многие потоки ожидают входа в синхронизированный блок, некоторые потоки должны ждать дольше, чем другие потоки. Следовательно, они не получают достаточно времени на вычисления, чтобы закончить свою работу вовремя.

5. Охраняемые блоки

67. Какие два метода, от которых наследуется каждый объект, java.lang.Objectможно использовать для реализации простого сценария производитель / потребитель?

Когда рабочий поток завершил свою текущую задачу, и очередь для новых задач пуста, он может освободить процессор, установив внутреннюю блокировку объекта очереди и вызвав метод wait(). Поток будет разбужен каким-либо потоком производителя, который поместил новую задачу в очередь и который снова получит ту же самую внутреннюю блокировку для объекта очереди и вызовет notify()ее.

68. В чем разница между notify()и notifyAll()?

Оба метода используются для пробуждения одного или нескольких потоков, которые усыпляют себя вызовом wait(). Пока notify()просыпается только один из ожидающих потоков, notifyAll()пробуждаются все ожидающие потоки.

69. Как определяется, какой поток просыпается при вызове notify()?

Не указано, какие потоки будут разбужены вызовом, notify()если более одного потока ожидает. Следовательно, код не должен полагаться на какую-либо конкретную реализацию JVM.

70.

Правильный ли следующий код, который извлекает целочисленное значение из некоторой реализации очереди?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public Integer getNextInt() {
    Integer retVal = null;
    synchronized (queue) {
        try {
            while (queue.isEmpty()) {
                queue.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized (queue) {
        retVal = queue.poll();
        if (retVal == null) {
            System.err.println("retVal is null");
            throw new IllegalStateException();
        }
    }
    return retVal;
}

Хотя приведенный выше код использует очередь в качестве монитора объектов, он не ведет себя правильно в многопоточной среде. Причина этого заключается в том, что он имеет два отдельных синхронизированных блока. Когда два потока просыпаются в строке 6 другим потоком, который вызывает notifyAll(), оба потока вводят один за другим второй синхронизированный блок. В этом втором блоке очередь теперь имеет только одно новое значение, следовательно, второй поток будет опрашивать пустую очередь и получать нулевое значение в качестве возвращаемого значения.

6. Неизменные объекты

71. Что такое неизменный объект

  • Неизменяемые объекты могут быть опубликованы через любой механизм
  • Неизменяемые объекты могут безопасно использоваться любым потоком без дополнительной синхронизации, даже если синхронизация не используется для их публикации.

72. Как создать неизменный объект

Для создания неизменяемого объекта вам необходимо:

  • Не добавляйте метод установки
  • Объявите все поля как окончательные и закрытые
  • Если поле является изменяемым объектом, создайте его защитные копии для методов получения
  • Если изменяемый объект, переданный конструктору, должен быть присвоен полю, создайте его защитную копию
  • Не позволяйте подклассам переопределять методы.

73. Каким правилам вы должны следовать, чтобы реализовать неизменный класс?

  • Все поля должны быть окончательными и приватными.
  • Должны быть не сеттерские методы.
  • Сам класс должен быть объявлен как final, чтобы подклассы не нарушали принцип неизменности.
  • Если поля имеют не примитивный тип, а ссылку на другой объект:
    • Не должно быть метода получения, который предоставляет ссылку непосредственно вызывающей стороне.
    • Не изменяйте ссылочные объекты (или, по крайней мере, изменение этих ссылок невидимо для клиентов объекта).

7. Блокировка объектов

74. Можно ли проверить, удерживает ли поток блокировку монитора на каком-либо заданном объекте?

Класс java.lang.Threadпредоставляет статический метод, Thread.holdsLock(Object)который возвращает true тогда и только тогда, когда текущий поток удерживает блокировку объекта, заданного в качестве аргумента для вызова метода.

75. Что мы понимаем под конфликтом блокировок?

Конфликт блокировки возникает, когда два или более потоков конкурируют в получении блокировки. Планировщик должен решить, разрешает ли он потоку, который ожидает ожидания, и выполняет переключение контекста, чтобы позволить другому потоку занять центральный процессор, или если ожидание занятого потока занято, ожидание более эффективно. Оба способа вводят простой в нижний поток.

76. Какие методы помогают уменьшить конфликт блокировок?

В некоторых случаях конкуренция за блокировку может быть уменьшена путем применения одного из следующих методов:

  • Размах замка уменьшен.
  • Количество раз получения определенной блокировки уменьшается (разделение блокировки).
  • Использование аппаратных поддерживаемых оптимистических операций блокировки вместо синхронизации.
  • Избегайте синхронизации, где это возможно.
  • Избегайте объединения объектов.

77. Какой метод уменьшения конкуренции за блокировку можно применить к следующему коду?

1
2
3
4
5
6
synchronized (map) {
    UUID randomUUID = UUID.randomUUID();
    Integer value = Integer.valueOf(42);
    String key = randomUUID.toString();
    map.put(key, value);
}

Приведенный выше код выполняет вычисление случайного UUID и преобразование литерала 42 в объект Integer в синхронизированном блоке, хотя эти две строки кода являются локальными для текущего потока и не влияют на другие потоки. Следовательно, они могут быть перемещены из синхронизированного блока:

1
2
3
4
5
6
UUID randomUUID = UUID.randomUUID();
Integer value = Integer.valueOf(42);
String key = randomUUID.toString();
synchronized (map) {
    map.put(key, value);
}

78. Поясните на примере технику блокировки расщепления.

Разделение блокировок может быть способом уменьшить конфликт блокировок, когда одна блокировка используется для синхронизации доступа к различным аспектам одного и того же приложения. Предположим, у нас есть класс, который реализует вычисление некоторых статистических данных нашего приложения. Первая версия этого класса использует ключевое слово, синхронизированное в каждой сигнатуре метода, чтобы защитить внутреннее состояние перед повреждением несколькими параллельными потоками. Это также означает, что каждый вызов метода может вызвать конфликт блокировки, так как другие потоки могут пытаться получить одну и ту же блокировку одновременно. Но может быть возможно разделить блокировку на экземпляре объекта на несколько меньших блокировок для каждого типа статистических данных в каждом методе. Следовательно, поток T1, который пытается увеличить статистические данные D1, не должен ждать блокировки, в то время как поток T2 одновременно обновляет данные D2.

79. Что мы понимаем под чередованием замков?

В отличие от разделения блокировок, когда мы вводим разные блокировки для разных аспектов приложения, в чередовании блокировок используются несколько блокировок для защиты разных частей одной и той же структуры данных. Примером этой техники является класс ConcurrentHashMapиз java.util.concurrentпакета JDK . MapРеализация использует внутренне различные ковши для хранения своих ценностей. Ведро выбирается ключом значения. ConcurrentHashMapтеперь использует разные блокировки для защиты разных блоков хешей. Следовательно, один поток, который пытается получить доступ к первому сегменту хэша, может получить блокировку для этого сегмента, в то время как другой поток может одновременно получить доступ ко второму сегменту. В отличие от синхронизированной версии HashMapэтот метод может повысить производительность, когда разные потоки работают на разных сегментах.

80. Что такое интерфейс блокировки в Java Concurrency API?

Интерфейс java.util.concurrent.locks.Lock используется в качестве механизма синхронизации потоков, аналогичного синхронизированным блокам. Новый механизм блокировки является более гибким и предоставляет больше возможностей, чем синхронизированный блок.

Основные различия между замком и синхронизированным блоком следующие:

  • Гарантия последовательности? Синхронизированный блок не дает никакой гарантии последовательности, в которой ожидающему потоку будет предоставлен доступ. Интерфейс блокировки обрабатывает это.
  • Нет времени ожидания? Синхронизированный блок не имеет опции тайм-аута, если блокировка не предоставлена. Интерфейс блокировки предоставляет такую ​​возможность.
  • Единый метод? Синхронизированный блок должен полностью содержаться в одном методе, тогда как методы интерфейса блокировки lock () и unlock () могут вызываться в разных методах.

8.Executors

8.1 Интерфейсы исполнителя

81. Плюсы ExecutorService за таймер

  • Таймер не может использовать преимущества доступных процессорных ядер в отличие от ExecutorService, особенно с несколькими задачами, использующими разновидности ExecutorService, такие как ForkJoinPool
  • ExecutorService предоставляет API для совместной работы, если вам нужна координация между несколькими задачами. Предположим, что вам необходимо отправить N рабочих задач и дождаться завершения всех из них. Вы можете легко достичь этого с помощью invokeAll API. Если вы хотите добиться того же с несколькими задачами таймера, это было бы не просто.
  • ThreadPoolExecutor предоставляет лучший API для управления жизненным циклом потока.

82. Какая связь между двумя интерфейсами Executor и ExecutorService?

Интерфейс Executorопределяет только один метод: execute(Runnable). Реализации этого интерфейса должны будут выполнить данный экземпляр Runnable в будущем. ExecutorServiceИнтерфейс является расширением Executorинтерфейса и предоставляет дополнительные методы , чтобы закрыть основной реализации, дождаться прекращения всех представленных задач и позволяет представления экземпляров Callable.

83. Что происходит, когда вы submit()выполняете новую задачу для экземпляра ExecutorService, очередь которого уже заполнена?

Как submit()указывает сигнатура метода , ExecutorServiceреализация должна генерировать a RejectedExecutionException.

84. Что такое ScheduledExecutorService?

Интерфейс ScheduledExecutorServiceрасширяет интерфейс ExecutorServiceи добавляет метод, который позволяет отправлять новые задачи в базовую реализацию, которая должна выполняться в определенный момент времени. Существует два метода для планирования одноразовых задач и два метода для создания и выполнения периодических задач.

Узнайте больше здесь:

Пример Java CompletionService

Пример Java ExecutorService — руководство

Пример Java ScheduledExecutorService

Пример Java RunnableScheduledFuture

Пример java.util.concurrent.ThreadFactory

java.util.concurrent.RejectedExecutionException — Как решить RejectedExecutionException

Пример обменника, передающий логи в фоновый логгер

Пример java.util.concurrent.RejectedExecutionHandler

Пример java.util.concurrent.ScheduledThreadPoolExecutor

8.2. Пулы потоков

85. Знаете ли вы простой способ построить пул потоков из 5 потоков, который выполняет некоторые задачи, которые возвращают значение?

SDK предоставляет фабричный и служебный класс Executors, статический метод которого newFixedThreadPool(int nThreads)позволяет создавать пул потоков с фиксированным числом потоков (реализация MyCallableопущена):

01
02
03
04
05
06
07
08
09
10
11
12
public static void main(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    Future<Integer>[] futures = new Future[5];
    for (int i = 0; i < futures.length; i++) {
        futures[i] = executorService.submit(new MyCallable());
    }
    for (int i = 0; i < futures.length; i++) {
        Integer retVal = futures[i].get();
        System.out.println(retVal);
    }
    executorService.shutdown();
}

86. Можно ли выполнять потоковые операции в Java 8 с пулом потоков?

Коллекции предоставляют метод parallelStream()для создания потока, который обрабатывается пулом потоков. В качестве альтернативы вы можете вызвать промежуточный метод parallel()для данного потока, чтобы преобразовать последовательный поток в параллельный аналог.

87. Всегда ли пул объектов улучшает производительность многопоточных приложений?

Пулы объектов, которые пытаются избежать создания новых объектов путем их объединения, могут повысить производительность однопоточных приложений, поскольку затраты на создание объектов чередуются путем запроса нового объекта из пула. В многопоточных приложениях такой пул объектов должен иметь синхронизированный доступ к пулу, и дополнительные затраты на конфликты блокировок могут перевесить сэкономленные затраты на дополнительное строительство и сборку мусора новых объектов. Следовательно, пул объектов не всегда может улучшить общую производительность многопоточного приложения.

8.3 Форк / Регистрация

88. Как мы можем получить доступ к пулу потоков, который используется операциями параллельного потока?

Пул потоков, используемый для параллельных потоковых операций, доступен ForkJoinPool.commonPool(). Таким образом, мы можем запросить его уровень параллелизма с commonPool.getParallelism(). Уровень не может быть изменен во время выполнения , но он может быть сконфигурирован, предоставляя следующий параметр JVM: -Djava.util.concurrent.ForkJoinPool.common.parallelism=5.

89. Какие задачи можно решить с помощью инфраструктуры Fork / Join?

Базовый класс Fork / Join Framework java.util.concurrent.ForkJoinPool— это в основном пул потоков, который выполняет экземпляры java.util.concurrent.ForkJoinTask. Класс ForkJoinTaskпредоставляет два метода fork()и join(). Хотя fork()используется для запуска асинхронного выполнения задачи, метод join()используется для ожидания результата вычислений. Следовательно, инфраструктура Fork / Join может использоваться для реализации алгоритмов «разделяй и властвуй», где более сложная задача делится на ряд более мелких и более простых для решения проблем.

90. Можно ли найти наименьшее число в массиве чисел с помощью Fork / Join-Framework?

Проблема нахождения наименьшего числа в массиве чисел может быть решена с помощью алгоритма «разделяй и властвуй». Самая маленькая проблема, которая может быть решена очень легко, — это массив из двух чисел, поскольку мы можем определить меньшее из двух чисел непосредственно одним сравнением. Используя подход «разделяй и властвуй», исходный массив делится на две части одинаковой длины, и обе части предоставляются двум экземплярам RecursiveTask, расширяющим классForkJoinTask, Разветвив две задачи, они выполняются и либо решают проблему напрямую, если их срез массива имеет длину два, либо они снова рекурсивно делят массив на две части и разбивают две новые задачи RecursiveTasks. Наконец, каждый экземпляр задачи возвращает свой результат (путем непосредственного вычисления или ожидания двух подзадач). Корневые задачи затем возвращают наименьшее число в массиве.

91. В чем разница между двумя классами RecursiveTaskи RecursiveAction?

В отличие от RecursiveTaskметода compute()из RecursiveActionне должен возвращать значение. Следовательно, RecursiveActionможет использоваться, когда действие работает непосредственно с некоторой структурой данных без необходимости возврата вычисленного значения.

Узнайте больше здесь:

Пример java.util.concurrent.RecursiveTask

Пример java.util.concurrent.ForkJoinPool

Пример java.util.concurrent.ForkJoinWorkerThread

9. Параллельные Коллекции

92. Что такое классы одновременной коллекции?

Классы Java Collection являются отказоустойчивыми, что означает, что если коллекция будет изменена в то время, когда какой-либо поток будет проходить по ней с помощью итератора, iterator.next () сгенерирует исключение ConcurrentModificationException.

93. Что такое модель памяти Java?

Модель памяти Java описывает, как потоки в языке программирования Java взаимодействуют через память. Вместе с описанием однопоточного исполнения кода модель памяти обеспечивает семантику языка программирования Java.

94. В чем разница между HashMapи Hashtableособенно в отношении безопасности потоков?

Методы Hashtableвсе синхронизированы. Это не тот случай для HashMapреализации. Следовательно, Hashtableявляется потокобезопасным, тогда HashMapкак не является потокобезопасным. Поэтому для однопоточных приложений более эффективно использовать «более новую» HashMapреализацию.

95. Есть ли простой способ создать синхронизированный экземпляр произвольной реализации Collection, Listили Map?

Коллекции утилита класса предоставляет методы synchronizedCollection(Collection), synchronizedList(List)и synchronizedMap(Map)которые возвращают поточно-коллекция / список / карту , которая подкреплена данного экземпляра.

Узнайте больше здесь:

Пример параллельных массивов Java 8

Пример java.util.concurrent.ConcurrentNavigableMap

Пример java.util.concurrent.ConcurrentSkipListMap

Пример java.util.concurrent.CopyOnWriteArraySet

Пример java.util.concurrent.CopyOnWriteArrayList

10.BlockingQueue

96. Что такое BlockingQueue?

BlockingQueue — это Java-очередь, поддерживающая операции, которые ожидают, когда очередь станет непустой, при извлечении и удалении элемента, и ожидают, когда пространство станет доступным в очереди при добавлении элемента. Интерфейс TransferQueue был добавлен. Это уточнение интерфейса BlockingQueue, в котором производители могут ждать, пока потребители получат элементы.

Узнайте больше здесь:

Пример Java BlockingQueue

Пример java.util.concurrent.DelayQueue

Пример java.util.concurrent.LinkedBlockingQueue

Пример Java.util.concurrent.SynchronousQueue

Пример java.util.concurrent.ArrayBlockingQueue

Пример java.util.concurrent.locks.AbstractQueuedSynchronizer

Пример блокировки очереди для выполнения команд

Пример очереди блокировки ограниченного пула соединений

Пример синхронной очереди для выполнения команд

11. Атомные переменные

97. Что мы понимаем под операцией CAS?

CAS означает сравнение и замена и означает, что процессор предоставляет отдельную инструкцию, которая обновляет значение регистра, только если предоставленное значение равно текущему значению. Операции CAS можно использовать, чтобы избежать синхронизации, поскольку поток может попытаться обновить значение, предоставив его текущее значение и новое значение для операции CAS. Если другой поток тем временем обновил значение, значение потока не равно текущему значению, и операция обновления завершается неудачно. Затем поток читает новое значение и пытается снова. Таким образом, необходимая синхронизация сменяется оптимистическим ожиданием вращения.

98. Какие классы Java используют операцию CAS?

Классы SDK в пакете java.util.concurrent.atomicпохожи AtomicIntegerили AtomicBooleanиспользуют внутреннюю операцию CAS для реализации одновременного увеличения.

01
02
03
04
05
06
07
08
09
10
11
public class CounterAtomic {
    private AtomicLong counter = new AtomicLong();
    public void increment() {
        counter.incrementAndGet();
    }
    public long get() {
        return counter.get();
    }
}

Узнайте больше здесь:

Пример java.util.concurrent.atomic.AtomicBoolean

Пример java.util.concurrent.atomic.AtomicLongArray

Пример Java AtomicIntegerArray

Пример Java AtomicInteger

Пример Java AtomicMarkableReference

Пример Java AtomicReference

12. Параллельные случайные числа

99. Какова цель занятия java.lang.ThreadLocal?

Поскольку память распределяется между различными потоками, ThreadLocalобеспечивает способ хранения и извлечения значений для каждого потока в отдельности. Реализации ThreadLocalхранят и извлекают значения для каждого потока независимо, так что, когда поток A сохраняет значение A1, а поток B сохраняет значение B1 в одном и том же экземпляре ThreadLocal, поток A позднее получает значение A1 из этого ThreadLocalэкземпляра, а поток B получает значение B1.

100. Для чего возможны варианты использования java.lang.ThreadLocal?

Экземпляры ThreadLocalмогут использоваться для передачи информации по всему приложению без необходимости передавать ее от метода к методу. Примерами могут быть транспортировка информации о безопасности / входе в систему в таком случае ThreadLocal, чтобы она была доступна каждому методу. Другим вариантом использования может быть передача информации о транзакции или вообще объектов, которые должны быть доступны во всех методах, без передачи их от метода к методу.

Узнайте больше здесь:

Пример java.util.concurrent.ThreadLocalRandom

Итак, теперь вы готовы к собеседованию! Не забудьте проверить наш БЕСПЛАТНЫЙ курс Академии Java Concurrency Essentials !

Если вам понравилось, подпишитесь на нашу новостную рассылку, чтобы получать еженедельные обновления и бесплатные обзоры! Кроме того, проверьте JCG Academy для более углубленного обучения!

Вы можете оставить свои комментарии, и мы включим их в статью! Включите их в статью!

Последнее обновление 26 января 2019 г.