Многопоточность.JAVA Flashcards

1
Q

Что такое многопоточность?

A

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

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

Многопоточность может работать с любым количеством ядер.

Чтобы создать дополнительный поток в Java, можно либо наследовать класс Thread, либо реализовать интерфейс Runnable. Для запуска потока необходимо вызвать метод start(), который создаст новый поток и выполнит метод run() в этом потоке.

В Джаве могопоточность представлена в виде виртуальной параллельности.
В одноядерном процессоре: Context Switch
В многоядерном процессоре: Context Switch + Parallelism

Синхронно - последовательно
Асинхронно - параллельно

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Что такое и чем процесс отличается от потока? (4)

A

Основное отличие между процессом и потоком заключается в том, что процесс является изолированным экземпляром любой программы с собственной памятью и контекстом исполнения, а поток является легковесным процессом, который работает в рамках основного процесса и имеет общую память с другими потоками. Процессы обычно более надежны и изолированы друг от друга, но потоки более эффективны и могут легче совместно использовать ресурсы компьютера.
Как создать поток?
1) Создать класс extends Thread.
2) Передать в new Thread() объект Runnable. Можно использовать анонимный класс или лямбда-выражение.

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

Поток - набор команд, выполняющийся на центральном процессоре. Потоки находятся внутри процесса.

Процесс гораздо тяжелее потока, его запуск и содержание обходится гораздо дороже.

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

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

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

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

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

JVM, испоняющая байткод - процесс, а GC в ней - поток

Каждый процесс имеет свой уникальный идентификатор (PID), а каждый поток имеет свой уникальный идентификатор (TID).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Чем Thread отличается от Runnable?

A

Thread и Runnable - это два способа создания и запуска потоков в Java.

  1. Класс || Интерфейс(множественное наследование)
  2. Методы || 1 абстрактный метод
  3. Свой стек вызовов || стек вызовов потока
  4. Можно запустить поток через метод .start() || Должны быть переданы в конструктор Тред и запущены через старт
  5. Можно остановить через стоп || можно остановить только через флаг прерывания
  6. Есть методы для синхронизации || нет методов
  7. Нельзя использовать в тред пуле || можно использовать в тред пуле

Runnable – это функциональный интерфейс с единственным методом public void run(). Экземпляр Runnable не является потоком, а просто формулирует задачу, которую можно выполнить в отдельном потоке.
Помимо того, что Runnable помогает разрешить проблему множественного наследования, несомненный плюс от его использования состоит в том, что он позволяет логически отделить логику выполнения задачи от непосредственного управления потоком.

Thread implements Runnable – это класс, содержащий методы для запуска и управления состоянием потока, экземпляр которого мы можем создать и запустить в отдельном потоке, переопределив метод run().

Класс Thread позволяет наследоваться от него и переопределять методы, в то время как интерфейс Runnable не предоставляет такой возможности.

Каждый экземпляр класса Thread имеет свой собственный стек вызовов, а объекты, реализующие интерфейс Runnable, используют стек вызовов своего потока.

Класс Thread позволяет запускать новый поток напрямую, вызывая метод start(), а объекты, реализующие интерфейс Runnable, должны быть переданы в конструктор Thread и запущены методом start().

Класс Thread позволяет остановить поток вызовом метода stop(), который может привести к непредсказуемому поведению, а объекты, реализующие интерфейс Runnable, могут быть остановлены только с помощью флага прерывания.

Класс Thread имеет ряд методов, связанных с управлением потоком, таких как sleep(), yield(), join() и interrupt(), которые не доступны в объектах, реализующих интерфейс Runnable.

Класс Thread имеет ряд методов, связанных с синхронизацией потоков, таких как wait(), notify() и notifyAll(), которые не доступны в объектах, реализующих интерфейс Runnable.

Использование класса Thread обычно требует больше ресурсов, чем использование объектов, реализующих интерфейс Runnable.

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

Каждый экземпляр класса Thread имеет свой уникальный идентификатор (TID), а объекты, реализующие интерфейс Runnable, не имеют собственного идентификатора и используют идентификатор своего потока.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Когда нужно использовать Thread, а когда Runnable?

A

Thread и Runnable используются для создания параллельных процессов в Java приложениях.

Используй Thread если
-Нужны методы синхронизации
-Если нужно задать Имя потока
-Если нужно выполнить задачи в основном потоке приложения
Используй Runnable если
-Если нужно выполнить задачу в фоне
-Если нужно использовать лямбду для создания потока
-Если нужно сформировать класс и унаследоваться от еще одного класса

Runnable является интерфейсом, который содержит единственный метод run(), который не возвращает значения. Для использования Runnable нужно создать объект, реализующий этот интерфейс, и передать его в Thread.

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

Thread предоставляет некоторые дополнительные методы для работы с потоками, такие как управление приоритетом и приостановкой.

Если вы планируете запустить несколько потоков, то лучше использовать Runnable, так как этот интерфейс может быть реализован несколькими классами, а Thread - только одним.

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

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

Используйте класс Thread, если вам нужно выполнить какие-то задачи в основном потоке приложения, например, для управления графическим интерфейсом пользователя (GUI).

Используйте класс Runnable, если вам нужно выполнить отдельную задачу в фоновом потоке приложения, не влияя на основной поток выполнения.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

что такое Мьютекс (Mutex)

A

Мьютекс (Mutex) в Java - это механизм синхронизации доступа к общим ресурсам между несколькими потоками. Простыми словами, мьютекс - это объект, который используется для блокировки доступа к общим ресурсам одним потоком в то время, когда другой поток уже выполняет работу с этим ресурсом.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Что такое монитор? Как монитор реализован в java?

A

Монитор это средство обеспечения контроля за доступом к ресурсам. У каждого объекта есть свой монитор.
Монитор встроен в класс Object и имеется у каждого объекта или класса. Он имеет 2 состояния: занят и свободен.

Монитор можно представить как обычное boolean поле. Следовательно если поле true, нет никаких препятствий чтобы поток выполнил synchronized метод или блок. И выставляет его сразу в false пока он не выйдет из данного метода или блока. Если другой поток пытается в это время вызвать synchronized метод или блок то это невозможно, и поток будет ждать его освобождения.

В Java монитор реализован с помощью ключевого слова synchronized, причем ни методы ни блоки не имеют монитора, используется монитор this в методе неявно, и в блоке явно. в static class используется монитор класса.

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Что такое синхронизация? Какие способы синхронизации существуют в java?

A

Синхронизация - это процесс координации работы нескольких потоков
Требуется для обеспечения правильного выполнения операций над общими ресурсами.
Цель синхронизации состоит в том, чтобы избежать состояний гонки (race conditions), которые могут возникать при параллельном доступе к общим ресурсам и могут приводить к непредсказуемому поведению программы.

В Java существует несколько способов синхронизации потоков:

1.synchronized
2.Методы wait() и notify() / notifyAll().
3.join()
4.Блокировки (Lock) и условные переменные (Condition).
5.Атомарные операции.
6.Коллекции из пакета java.util.concurrent.

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

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

Системная синхронизация с использованием join().

Метод join(), вызванный у экземпляра класса Thread, позволяет текущему потоку остановиться до того момента, как поток, связанный с этим экземпляром, закончит работу.

Блокировки (Lock) и условные переменные (Condition). Блокировки являются более гибким механизмом синхронизации, чем ключевое слово synchronized, так как они позволяют более точно управлять захватом и освобождением мониторов. Условные переменные позволяют потокам синхронизироваться на условиях, связанных с состоянием общих ресурсов, аналогично методам wait() и notify().

Атомарные операции. Они позволяют выполнять операции над переменными без возможности параллельного доступа к ним. В Java существует несколько классов, которые обеспечивают атомарность операций, такие как AtomicInteger и AtomicLong.

Коллекции из пакета java.util.concurrent. Эти коллекции обеспечивают безопасный доступ к общим ресурсам из нескольких потоков. Например, класс ConcurrentHashMap обеспечивает безопасную работу с хеш-таблицами из нескольких потоков.

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

В каких состояниях может находиться поток?

A

New - объект класса Thread создан, но еще не запущен. Он еще не является потоком выполнения и естественно не выполняется.

Runnable - поток готов к выполнению, но планировщик еще не выбрал его.

Running – поток выполняется.

Waiting/blocked/sleeping - поток блокирован или поток ждет окончания работы другого потока.

Dead - поток завершен. Будет выброшено исключение при попытке вызвать метод start() для dead потока.

public enum State (У класса Thread есть внутренний класс State - состояние, а также метод public State getState().)

1) NEW - создан, не стартовал.
2) RUNNABLE - работает или ждет в очереди на процессорное время.
3) BLOCKED - ждет мьютекс (synchronized{} или lock.lock()). Дождавшись, перейдет в RUNNABLE.
4) WAITING - при вызове thread.join(), object.wait(), condition.await(), когда ждет notify(All)/signal(All).
5) TIMED_WAITING - как WAITING, но также пробуждается, если прошло заданное время. Методы Thread.sleep, object.wait(time), thread.join(time), lock.tryLock(time), condition.await(time).
6) TERMINATED - когда закончился метод run() или случилось RuntimeException и вышло за пределы метода run().

}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Что обозначает ключевое слово volatile? Почему операции над volatile
переменными не атомарны?

A

volatile (изменчивый) – означает, что переменная может быть изменена.

volatile применимо только к примитивным типам данным и к ссылкам.

Переменная, объявленная как volatile, хранится в основной памяти (main memory), а не в кэше потока (thread cache). Это означает, что каждый поток, обращающийся к volatile переменной, получает доступ к единственной копии переменной в основной памяти, а не к локальной копии, хранящейся в кэше потока., гарантирует когерентность кэшей.

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

Однако, volatile не делает операцию присваивания атомарной.Там три операции: считать значение, присвоить новое, записать в память.

Операция, которая делает одно чтение/запись – атомарна.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Compare-And-Swap в Atomic

A

Java Atomic CAS (Compare-And-Swap) является одним из механизмов синхронизации для многопоточной работы с общей памятью. Этот механизм позволяет обеспечивать атомарность операций чтения-изменения-записи (RMW) в разделяемой памяти.

Операция CAS состоит из трех операций:

Сравнение (compare): сравнивает текущее значение переменной с ожидаемым значением.
Обновление (update): если текущее значение переменной равно ожидаемому, то значение переменной обновляется новым значением.
Возврат (return): возвращается булево значение, которое указывает на успешность выполнения операции.
Если два или более потоков одновременно пытаются выполнить операцию CAS на одной и той же переменной, только один из них успешно выполнит операцию, а остальные будут возвращены с неудачным результатом.

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Что такое Атомарные операции в Java.

A

Примером атомарной операции в Java может служить инкрементация (увеличение значения на 1) переменной типа AtomicInteger.

public class Example {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        int oldValue = counter.getAndIncrement();
        System.out.println("Old value = " + oldValue + ", new value = " + counter.get());
    }
}

В этом примере переменная counter типа AtomicInteger инициализируется значением 0. Затем в методе main() происходит инкрементация переменной с помощью метода getAndIncrement(), который возвращает предыдущее значение переменной и увеличивает его на 1. Таким образом, в переменной counter после выполнения этой операции сохранится новое значение, равное предыдущему значению плюс 1.

Метод getAndIncrement() является атомарной операцией, что означает, что он выполняется целиком и никакой другой поток не может одновременно изменять значение переменной counter. Это гарантирует корректность работы программы в многопоточной среде.

Атомарная операция в Java - это операция, которая выполняется как единое целое, без прерываний и не может быть разделена на более мелкие операции. Такие операции гарантируют, что в случае многопоточности другие потоки не могут изменять данные во время выполнения атомарной операции. Примерами атомарных операций в Java являются чтение и запись примитивных типов данных (кроме long и double) и операции из класса java.util.concurrent.atomic.

В Java атомарные операции поддерживаются через классы-обертки, которые обеспечивают атомарный доступ к переменным типа byte, short, int, long, boolean и ссылочным типам. Например, классы AtomicBoolean, AtomicInteger, AtomicLong и т.д.

Некоторые из атомарных операций, которые можно выполнить на этих классах-обертках, включают в себя:

get() - чтение значения переменной
set() - установка значения переменной
getAndSet() - чтение и установка значения переменной за одну операцию
compareAndSet() - сравнение текущего значения переменной с ожидаемым значением и, если они совпадают, установка нового значения переменной
incrementAndGet() - инкрементирование значения переменной и возврат нового значения
Эти операции являются атомарными, поскольку они выполняются за один шаг без возможности прерывания и гарантируют правильность результатов даже в многопоточной среде. Однако, если вы хотите выполнить несколько атомарных операций одновременно, вы можете использовать блокировки или другие механизмы синхронизации, чтобы обеспечить атомарность выполнения группы операций.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

как устроен инкремент в атомик?

A

Атомарность операции инкремента в классе AtomicLong достигается за счет использования механизма CAS (Compare-And-Swap).
Этот механизм позволяет сравнить текущее значение переменной с заданным значением и, если они совпадают, заменить его на новое значение.
Если же значения не совпадают, операция не выполняется и повторяется заново.

  1. Создать экземпляр класса AtomicLong и проинициализировать начальным значением.
  2. Вызвать метод incrementAndGet() для выполнения операции инкремента.
  3. Значение переменной atomicLong увеличится на 1 и вернется новое значение.

В Java атомарные операции выполняются без блокировки, что позволяет нескольким потокам одновременно выполнять операции над одним и тем же объектом. Один из примеров атомарных операций - это операция инкремента (увеличения значения на 1).

Атомарный инкремент в Java реализуется с помощью класса AtomicLong, который предоставляет атомарную версию операции инкремента над переменной типа long.

Для выполнения атомарного инкремента необходимо сделать следующее:

Создать экземпляр класса AtomicLong и проинициализировать его начальным значением:

Вызвать метод incrementAndGet() для выполнения операции инкремента. Этот метод инкрементирует значение переменной и возвращает ее новое значение:

В результате выполнения этой операции, значение переменной atomicLong увеличится на 1 и вернется новое значение.

Атомарность операции инкремента в классе AtomicLong достигается за счет использования механизма CAS (Compare-And-Swap). Этот механизм позволяет сравнить текущее значение переменной с заданным значением и, если они совпадают, заменить его на новое значение. Если же значения не совпадают, операция не выполняется и повторяется заново.

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Для чего нужны Atomic типы данных? Чем отличаются от volatile?

A

Чтобы безопасно выполнять операции при параллельных вычислениях в нескольких потоках не используя при этом ни блокировок, ни синхронизацию synchronized.

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

Могут быть использованы как улучшеные volatile переменные

C другой стороны, class AtomicInteger предоставляет атомарный метод для выполнения таких комплексных операций атомарно, например getAndIncrement() – атомарная замена оператора инкремента, его можно использовать, чтобы атомарно увеличить текущее значение на один.
Похожим образом сконструированы атомарные версии и для других типов данных.

Операция называется атомарной, если её можно безопасно выполнять при параллельных вычислениях в нескольких потоках, не используя при этом ни блокировок, ни синхронизацию synchronized.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Чем Runnable отличается от Callable?

A

Оба интерфейса предназначены для представления задачи, которая может выполняться несколькими потоками однако, между ними есть несколько отличий:

RUNNABLE || CALLABLE
не возвращает || возвращает
не обрабатывает || обрабатывает исключения
не поддерживает || дженерики поддерживает

Возвращаемое значение: метод run() интерфейса Runnable не возвращает никакого значения, тогда как Callable.call() возвращает объект Future, который может содержать результат вычислений;

Обработка исключений: метод run() не может выбрасывать проверяемые исключения, а метод call() может. В случае выброса исключения, вызывающий поток должен его обработать.

Поддержка Generic: интерфейс Callable параметризован типом возвращаемого значения, что позволяет использовать Generic.

Способ использования: Runnable используется в основном с Executor и Thread, в то время как Callable используется в основном с ExecutorService.

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

Если нужно возвращать результат и бросать исключения - Использовать Callable
Если не нужно чтобы возвращал результат то Runnable

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Как работает Thread.join()? Для чего он нужен?

A

Метод join() в Java используется для ожидания завершения выполнения потока, на котором он был вызван.

Когда поток вызывает метод join() для другого потока, он ожидает, пока поток, на котором был вызван join(), завершит свое выполнение. Это означает, что выполнение текущего потока приостанавливается до тех пор, пока поток, на котором был вызван join(), не завершится.
Пример использования метода join():

Thread thread1 = new Thread(() -> {
    // выполнение кода потока 1
});
Thread thread2 = new Thread(() -> {
    // выполнение кода потока 2
});
// запуск потоков
thread1.start();
thread2.start();

// ожидание завершения потока 1
try {
    thread1.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

// выполнение кода после завершения потока 1
В приведенном выше примере поток 1 запускается в отдельном потоке, а затем вызывается метод join(), чтобы поток 2 не начал свое выполнение, пока поток 1 не завершится. После завершения потока 1 выполнение кода продолжится.

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

Thread.join() позволяет синхронизировать потоки, чтобы главный поток (или другой поток) не продолжал свою работу до тех пор, пока не завершится указанный поток.
void join()
void join(long millis) - с временем ожидания
void join(long millis, int nanos)
Применение: при распараллелили вычисления, вам надо дождаться результатов, чтобы собрать их в кучу и продолжить выполнение.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Что такое Thread Pool и ExecutorService?

A

Предположим у нас есть 2 работника на фабрике и есть 5 коробок. Коробки им нужно перенести с одного места на другое. И они начинают последовательно их переносить.

Executor (java.util.concurrent) интер
ExecutorService (java.util.concurrent) интер
AbstractExecutorService (java.util.concurrent) абст класс
ForkJoinPool (java.util.concurrent) класс
ThreadPoolExecutor (java.util.concurrent) класс
public class ScheduledThreadPoolExecutor класс

1) Тред пул - это класс, который создает и держит в себе несколько потоков для выполнения задач.
2) В пул добавляются задачи (объекты Runnable, Callable, FutureTask) методом execute(task).
3) Когда поток из пула освобождается, он берет задачу и исполняет ее. Затем берет следующую и так далее.

Thread Pool - позволяет повторно использовать созданные потоки для выполнения нескольких задач, что обеспечивает более эффективное использование ресурсов процессора и памяти.

В Java Thread Pool реализуется с помощью интерфейса Executor и его реализаций, таких как ThreadPoolExecutor и ScheduledThreadPoolExecutor.

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

Таким образом, Thread Pool является базовым механизмом для управления пулом потоков, в то время как ExecutorService предоставляет более высокоуровневые методы для управления пулом.

ExecutorService - это расширенный интерфейс Executor, который предоставляет более высокоуровневые методы для управления пулом потоков, такие как выполнение задач по расписанию, управление жизненным циклом потоков и т.д.

ExecutorService является более абстрактным интерфейсом, чем Thread Pool, и позволяет более гибко управлять пулом потоков.

ExecutorService может создавать и управлять Thread Pool, определять размер пула, задержку перед выполнением задачи, обрабатывать возвращаемые значения и исключения, а также отменять задачи.

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

Таким образом, использование Thread Pool и ExecutorService позволяет более эффективно использовать ресурсы процессора и обеспечить более быстрое и отзывчивое выполнение задач в многопоточной среде.
~~~
Executors.newSingleThreadExecutor(); один поток
Executors.newFixedThreadPool(5); устанавливаем количество потоков
Executors.newCachedThreadPool(); неограниченное количество кэшируемых потоков(после отправки новых заданий новые потоки не создаются, а достаются из кэша)
Executors.newScheduledThreadPool(3); отправка потоков по расписанию
Executors.newWorkStealingPool(); создание потока из форк джойна

17
Q

Что такое FutureTask? (5)

A

1) Класс java.util.concurrent.FutureTask implements RunnableFuture
2) Это отменяемая, асинхронная задача, которую можно передать потоку на выполнение.
3) Создав объект FutureTask, мы дальше можем executor.execute(fTask)

FutureTask - это класс в Java, который реализует интерфейс Future и позволяет асинхронно выполнить задачу и получить ее результат.

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

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

java.util.concurrent.Future - Этот класс предоставляет базовую реализацию Future, с методами для запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения результатов.( cancel / get / isCancelled / isDone )

Результат может быть получен только когда вычисление завершено, метод получения будет заблокирован, если вычисление ещё не завершено. Объекты FutureTask могут быть использованы для обёртки объектов Callable и Runnable. Так как FutureTask реализует Runnable, его можно передать в Executor на выполнение.

Объекты FutureTask могут быть использованы для обертки объектов Callable и Runnable.
Так как FutureTask помимо Future реализует Runnable, его можно передать в Executor на выполнение.

18
Q

Что такое deadlock?

A

Тред 1 - Лок 1 -> Лок 2
Тред 2 - Лок 2 -> Лок 1

Взаимная блокировка (deadlock) – явление, при котором 2 и более потоков находятся в режиме ожидания и ничего не делают.

Может произойти, когда соблюдаются все условия:
* взаимное исключение: по крайней мере один ресурс занят в syncronized режиме, и следовательно только один поток может использовать его в конкретный момент;
* удержание и ожидание: поток уже удерживает ресурс и запрашивает дополнительные syncronized ресурсы, которые могут удерживаються другими потоками;
* цикличного ожидания: поток ждет освобождения ресурса другим потоком, который в свою очередь ждет освобождения ресурса заблокированного первым потоком.
* отсутствия предочистки: операционная система не может отбирать ресурсы у процесса;

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

Предупреждение deadlock:
1. Иерархия ресурсов( Каким-то образом упорядочить ресурсы, например выстроить приоритет)
2. Ввод дополнительно монитора. если 2 заняты - то обратись к 3 монитору

Диагностика deadlock:

  1. Логгирование
  2. Thread dump
    в консоли вызвать:
    jps - Получить id всех запущенных в Java процессов
    jstack - поулчить thread dump конкретного процесса
  3. на win нажать ctrl+break в консоли
  4. JConsole
19
Q

Что такое livelock?

A

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

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

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

20
Q

Что такое race condition?

A

Race condition возникает, когда два или более потока одновременно обращаются к одному ресурсу или переменной, и результат их работы зависит от порядка выполнения операций. Результат такого взаимодействия потоков становится неопределенным и может привести к непредсказуемому поведению программы.

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

Data Race - ситуация, когда несколько потоков имеют доступ к переменной и хотябы один из них может ее изменить..
Lock Starvation – потоки не заблокированы, но есть нехватка ресурсов, из-за чего менее преоритетные потоки ждут либо очень долго, либо вечно.

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

21
Q

Что такое Фреймворк fork/join? Для чего он нужен?

A

Фреймворк Fork/Join, представленный в JDK 7, – это набор классов и интерфейсов, позволяющих использовать преимущества многопроцессорной архитектуры современных компьютеров.
В его основе лежит ForkJoinPool: имплементация ExecutorService, которая не создает отдельный поток под каждую подзадачу, а создает для каждого потока Queue или Deque с подзадачами. В частности это помогает реализовать ключевой алгоритма фреймворка:
workstealing - потоки, которые завершили выполнение собственных подзадач, могут «украсть» подзадачи у других потоков, которые все еще заняты.

У FJP есть три важных метода:

Метод fork() — разделяет процесс на части
Метод Join() склеивает все обратно и дожидается выполнения
Метод invokeAll() выполняет задачку, которую мы передадим и вернет результат выполнения.
Реализовать это можно либо наследуясь от RecursiveAction, либо от RecursiveTask.

Этап Fork: большая задача разделяется на несколько меньших подзадач, которые в свою очередь также разбиваются на меньшие. И так до тех пор, пока задача не становится тривиальной и решаемой последовательным способом.
Этап** Join**: далее (опционально) идет процесс «свертки» – решения подзадач некоторым образом объединяются, пока не получится решение всей задачи.

Решение всех подзадач (в т. ч. и само разбиение на подзадачи) происходит параллельно.

Ещё одно замечательное преимущество этого фреймворка заключается в том, что он использует work-stealing алгоритм: потоки, которые завершили выполнение собственных подзадач, могут «украсть» подзадачи у других потоков, которые всё ещё заняты.

Для решения некоторых задач этап Join не требуется. Например, для параллельного QuickSort – массив рекурсивно делится на все меньшие и меньшие диапазоны, пока не вырождается в тривиальный случай из 1 элемента. Хотя в некотором смысле Join будет необходим и тут, т. к. все равно остается необходимость дождаться, пока не закончится выполнение всех подзадач.

Fork-Join Framework - это фреймворк для параллельного выполнения вычислительно интенсивных задач в Java. Он предназначен для использования в многопроцессорных и многопоточных средах и позволяет автоматически распределять задачи между потоками для оптимального использования ресурсов.

Fork-Join Framework использует модель “разделяй и властвуй” (divide-and-conquer) для разделения большой задачи на более мелкие подзадачи, которые затем могут быть выполнены параллельно. После выполнения подзадач, результаты собираются вместе для получения результата всей задачи.

Основное преимущество использования Fork-Join Framework заключается в том, что он автоматически управляет потоками и оптимизирует распределение задач между ними. Это позволяет уменьшить накладные расходы на управление потоками и обеспечить более эффективное использование ресурсов процессора.

Fork-Join Framework также предоставляет набор инструментов для управления и контроля выполнения задач, включая методы для остановки выполнения задач, определения статуса выполнения и управления приоритетами.

22
Q

чет отличаются volatile от synchronized ?

A

Оба ключевых слова volatile и synchronized используются в Java для обеспечения потокобезопасности, но они решают разные проблемы.

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

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

Volatile быстро , но не надежно(не гарантирует атомарность операций, так как у нескольких потоков может быть одновременный доступ к переменным и Если несколько потоков одновременно обращаются к переменной, то могут возникнуть гонки данных (data race), когда один поток пытается прочитать или записать переменную в то время, когда другой поток уже выполняет аналогичную операцию.)

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

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

Поэтому рекомендуется использовать volatile для переменных, которые не требуют блокировок, а synchronized - для блоков кода, которые требуют синхронизации доступа к общим ресурсам.

23
Q

Что означает ключевое слово synchronized? Где и для чего может использоваться?

A

synchronized используется для предотвращения ситуации гонок данных (race condition) и взаимной блокировки (deadlock), когда несколько потоков пытаются одновременно получить доступ к общим ресурсам.

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

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

Когда метод помечен как synchronized, он блокирует доступ к всему объекту, на котором вызывается метод.

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

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

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

24
Q

Как синхронизировать несколько методов?

A

Синхронизировать их по монитору одного объекта.

25
Q

Что является монитором у статического синхронизированного метода?

A

Объект типа Class, соответствующий классу, в котором определен метод.

26
Q

Что является монитором у нестатического синхронизированного класса?

A

Объект this.

27
Q

java.util. Concurrent поверхностно.

A

java.util.concurrent - это пакет в Java, который содержит классы и интерфейсы для работы с параллельным программированием и синхронизацией многопоточных приложений.

Более высокоуровневый API для работы с многопоточностью.

Классы и интерфейсы пакета java.util.concurrent можно объединить в несколько групп по функциональному признаку:
Сollections : набор эффективно работающих в многопоточной среде коллекций (CopyOnWriteArrayList/Set, ConcurrentHashMap, …)

CopyOnWriteArrayList:
* volatile массив внутри;
* при создании итератора нам возвращается неизменяемый оттиск листа для чтения, а при изменении создается полная копия листа: такой механизм предотвращает ConcurrentModificationException
* fail-fast итератор;
* модификация через iterator невозможна – UnsupportedOperationException.
* lock только при модификации списка, поэтому операции чтения очень быстрые;
ConcurrentHashMap:
- volatile entries
- усложнилась hash функция
- сегментация: сегмент - класс, группа HashMap’ов. Количество сегментов по умолчанию равно 16. Если пара key-value хранится в 10-ом сегменте, то ConcurrentHashMap заблокирует при необходимости только 10-й сегмент, и не будет блокировать остальные 15.

Synchronizers – объекты синхронизации, позволяющие разработчику управлять и/или ограничивать работу нескольких потоков. Cодержит пять объектов синхронизации:
semaphore, countDownLatch, ciclycBarrier, exchanger, phaser.

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

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

Locks – механизмы синхронизации потоков, альтернативы базовым synchronized, wait, notify, notifyAll: Lock, Condition, ReadWriteLock.

Executors включает средства, называемые сервисами исполнения, позволяющие управлять потоковыми задачами с возможностью получения результатов через интерфейсы Future и Callable.

ExecutorService служит альтернативой классу Thread, предназначенному для управления потоками. В основу сервиса исполнения положен интерфейс Executor, в котором определен один метод:
void execute(Runnable thread);
При вызове метода execute исполняется поток thread.

28
Q

Что такое Semaphore?

Какой класс реализует семафор в java? Какие его методы вы знаете?

A

Semaphore используется для защиты дорогих ресурсов, которые доступны в ограниченном количестве, например подключение к базе данных в пуле.
~~~
Semaphore(int permits)

Semaphore(int permits, boolean fair)
~~~
Параметр permits указывает на количество допустимых разрешений для доступа к ресурсу. Параметр fair во втором конструкторе позволяет установить очередность получения доступа. Если он равен true, то разрешения будут предоставляться ожидающим потокам в том порядке, в каком они запрашивали доступ. Если же он равен false, то разрешения будут предоставляться в неопределенном порядке.

1) Семафор - это объект, который может “захватываться” каким-то одним или несколькими потоками.
2) В семафоре определено некое число - количество потоков, которые могут одновременно пользоваться, “захватывать” этот семафор. Как правило это счетчик, который уменьшается на единицу с каждым новым потоком. Если счетчик меньше или равен нулю - семафор освобождается и пускает к себе новый “ожидающий захвата” поток.
3) Самый очевидный пример семафора - это менеджеры закачек - добавляем много файлов на закачку, но ограничиваем количество одновременно выполняемых закачек.

Реализация:

java.util.concurrent.Semaphore
1) acquire
2) acquireUninterruptibly
3) tryAcquire
4) release
5) availablePermits
6) isFair

29
Q

Как работают методы wait(), notify() и notifyAll()?

A

Методы wait(), notify() и notifyAll() в Java используются для организации синхронизации и взаимодействия между потоками.

Являются методами класса Обджект, а соответственно есть у объекта любого класса в Java.

Методы wait(), notify() и notifyAll() могут вызываться только из синхронизированного контекста (т.е. внутри блока synchronized), иначе будет выброшено исключение IllegalMonitorStateException.

Метод wait() вызывается на объекте монитора и блокирует выполнение текущего потока до тех пор, пока другой поток не вызовет метод notify() или notifyAll() на этом же объекте монитора. При этом текущий поток освобождает монитор и встает в очередь ожидания на его захват.
Когда вызван метод wait(), поток освобождает блокировку на объекте и переходит из состояния Работающий (Running) в состояние Ожидания (Waiting).

Метод notify() вызывается на объекте монитора и разблокирует один из потоков, ожидающих этого монитора. Если ни один из потоков не ожидает этот монитор, то метод ничего не делает.
Метод notify()подаёт сигнал одному из потоков, ожидающих на объекте, чтобы перейти в состояние Работоспособный (Runnable). При этом невозможно определить, какой из ожидающих потоков должен стать работоспособным.

Метод notifyAll() вызывается на объекте монитора и разблокирует все потоки, ожидающие этого монитора. Если ни один из потоков не ожидает этот монитор, то метод ничего не делает.
Метод notifyAll() заставляет все ожидающие потоки для объекта вернуться в состояние Работоспособный (Runnable).

Если ни один поток не находится в ожидании на методе wait(), то при вызове notify() или notifyAll() ничего не происходит.

30
Q

чем wait отличается от sleep?

A

Методы wait() и sleep() в Java отличаются своими основными целями и особенностями работы.

wait() и sleep() используются в разных целях и имеют разные особенности работы.
wait() используется для синхронизации и взаимодействия между потоками, а sleep() - для временных задержек.

-метод sleep() - приостанавливает поток на указанное время. Состояние меняется на WAITING, по истечению - RUNNABLE.
-не освобождает монитор, а просто приостанавливает выполнение потока на заданное время.
-также может вызвать InterruptedException, если текущий поток был прерван другим потоком во время приостановки, но этот метод может обрабатывать это исключение и продолжать выполнение.

-метод wait() - меняет состояние потока на WAITING. Метод wait() используется для синхронизации и взаимодействия между потоками. Он блокирует текущий поток до тех пор, пока другой поток не вызовет метод notify() или notifyAll() на том же объекте монитора. Это позволяет потокам ожидать определенных условий и выполнять действия в синхронизированном режиме.
- не принимает аргументов
- освобождает монитор объекта, на котором он вызывается, и встает в очередь
- может вызвать InterruptedException, если текущий поток был прерван другим потоком во время ожидания.

31
Q

Что такое потоки демоны? Для чего они нужны? Как создать поток-демон?

A

Потоки-демоны работают в фоновом режиме вместе с программой, но не являются неотъемлемой частью программы. Если какой-либо процесс может выполняться на фоне работы основных потоков выполнения и его деятельность заключается в обслуживании основных потоков приложения, то такой процесс может быть запущен как поток-демон с помощью метода setDaemon(boolean value), вызванного у потока до его запуска. Метод boolean isDaemon() позволяет определить, является ли указанный поток демоном или нет. Базовое свойство потоков-демонов заключается в возможности основного потока приложения завершить выполнение с окончанием кода метода main(), не обращая внимания на то, что поток-демон еще работает.

В Java есть два типа потоков: пользовательские (user threads) и демоны (daemon threads). Пользовательские потоки работают до тех пор, пока программа не завершится. Демоны, с другой стороны, работают в фоновом режиме и завершаются, когда все пользовательские потоки завершаются.

Чтобы создать поток в Java, нужно создать объект класса Thread и запустить его метод start(). Например:

Для создания демона нужно установить флаг setDaemon() объекта Thread перед запуском потока:

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

Потоки-демоны (daemon threads) в Java - это потоки, которые выполняются в фоновом режиме и не мешают основному потоку программы. Когда все пользовательские потоки завершают работу, потоки-демоны автоматически завершаются без ожидания завершения выполнения своих задач.

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

1) Поток-демон - это “обслуживающий” поток, который работает только система относительно свободна.
2) Пример - сборщик мусора в java.
3) Любой поток можно установить демоном, вызвав thread.setDaemon(true).
4) Если в приложении остались только потоки-демоны, то виртуальная машина завершит работу программы.

32
Q

Что такое RentrantLock?

A

ReentrantLock - это класс в Java, который предоставляет реализацию интерфейса Lock для управления блокировками в многопоточной среде.

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

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

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

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

33
Q

Потокобезопасные коллекции

A

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

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

ConcurrentLinkedQueue - это реализация очереди, которая использует атомарные операции для безопасного доступа в многопоточной среде. Это хороший выбор для сценариев, когда несколько потоков могут пытаться добавить элементы в очередь или извлекать их одновременно.

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

ConcurrentSkipListMap - это реализация TreeMap, которая также может использоваться в многопоточной среде. Она обеспечивает доступ в несколько потоков, сохраняя элементы в отсортированном порядке.

ArrayBlockingQueue - это реализация BlockingQueue, которая использует массив для хранения элементов. Она блокируется, когда очередь заполнена, и блокируется, когда очередь пуста.

ConcurrentHashMap.KeySetView - это представление множества ключей в ConcurrentHashMap. Оно предоставляет безопасный доступ к множеству ключей, хранящихся в ConcurrentHashMap.

34
Q

Блокировки (Lock) и условные переменные (Condition).

A

Блокировки (Lock) - это явные механизмы синхронизации, которые должны быть захвачены и освобождены явно в коде программы, с помощью методов lock() и unlock().
более гибкая и мощная схема синхронизации в сравнении с synchronized

представлены интерфейсом java.util.concurrent.locks.Lock и его реализациями, такими как ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock.

Условные переменные (Condition) - это механизм синхронизации потоков, который позволяет потокам ожидать выполнения определенного условия, прежде чем продолжить работу.

представлены интерфейсом java.util.concurrent.locks.Condition и его реализациями, такими как ReentrantLock.newCondition().