14-15. Основы многопоточного программирования Flashcards
Чем отличается процесс от потока?
Процесс — это совокупность кода и данных, разделяющих общее виртуальное адресное пространство.
Или если проще, какая-то сущность, кот. есть в ОС и есть какая-то конкретная программа, которая его юзает.
Чаще всего одна программа состоит из одного процесса (как процесс - программа), но бывают и исключения (например, браузер Chrome создает отдельный процесс для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга). Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен (взаимодействие между процессами осуществляется с помощью специальных средств).
На основе процессов можно пользоваться одновременно несколькими программами на ПК.
Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.
Один поток – это одна единица исполнения кода. Это наименьшая единица диспетчеризации уже в самом процессе. В каждом процессе мб несколько потоков. (и они могут иметь общий доступ к ресурсам - памяти, места и пр.)
Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса.
Мы работаем с потоками! (нить, поток, thread)
Каким образом можно создать поток?
Каждый процесс имеет хотя бы один выполняющийся поток. Тот поток, с которого начинается выполнение программы, называется главным. В языке Java, после создания процесса, выполнение главного потока начинается с метода main(). Затем, по мере необходимости, в заданных программистом местах, и при выполнении заданных им же условий, запускаются другие, побочные потоки.
В языке Java поток представляется в виде объекта-потомка класса Thread. Этот класс инкапсулирует стандартные механизмы работы с потоком.
Запустить новый поток можно двумя способами:
Способ 1: java.lang.Thread - через класс (к слову, имплементирующий Раннейбл!)
Создать объект класса Thread, передав ему в конструкторе нечто, реализующее интерфейс Runnable. Этот интерфейс содержит метод run(), который будет выполняться в новом потоке. Поток закончит выполнение, когда завершится его метод run().
private static class ExampleThread extends Thread { @Override public void run() { } }
и создание напрямую:
Thread thread1 = new ExampleThread();
thread1.start()
Способ 2: java.lang.Runnable - через интерфейс
Создать потомка класса Thread и переопределить его метод run():
private static class ExampleRunnable implements Runnable { @Override public void run() { } }
создание через обернутый раннейбл
Thread thread2 = new Thread(new ExampleRunnable()); thread2.start();
Через java.lang.Thread нужно, когда мы хотим пользоваться методами и полями тред-класса (старт, экзит, интеррапт, нексттредАйди и пр.)
Как мы можем получить текущий поток?
С помощью метода в классе Thread.currentThread();
Thread thread = Thread.currentThread();
System.out.println(“Thread name: “ + thread.getName());
System.out.println(“Thread priority: “ + thread.getPriority());
System.out.println(“Thread group name: “ + thread.getThreadGroup().getName());
Что делают методы isAlive и join?
boolean isAlive() — возвращает true если поток выполняется и false если поток еще не был запущен или был завершен.
“Tests if this thread is alive. A thread is alive if it has been started and has not yet died.”
public final native boolean isAlive();
IsAliveExample isAliveExample = new IsAliveExample(); //runnable Thread thread = new Thread(isAliveExample); thread.start();
while (thread.isAlive()) { /* ожидаем пока isAlive не станет false */ }
Метод join()
В Java предусмотрен механизм, позволяющий одному потоку ждать завершения выполнения другого. Для этого используется метод join(). Например, чтобы главный поток подождал завершения побочного потока myThready, необходимо выполнить инструкцию myThready.join() в главном потоке. Как только поток myThready завершится, метод join() вернет управление, и главный поток сможет продолжить выполнение.
public final void join() throws InterruptedException
public final synchronized void join(long millis)
throws InterruptedException
public final synchronized void join(long millis, int nanos)
throws InterruptedException
Метод join() имеет перегруженную версию, которая получает в качестве параметра время ожидания. В этом случае join() возвращает управление либо когда завершится ожидаемый поток, либо когда закончится время ожидания. Подобно методу Thread.sleep() метод join может ждать в течение миллисекунд и наносекунд – аргументы те же.
С помощью задания времени ожидания потока можно, например, выполнять обновление анимированной картинки пока главный (или любой другой) поток ждёт завершения побочного потока, выполняющего ресурсоёмкие операции.
Необходимое условие работы метода join() - обернуть в конструкцию try-catch. Вызывающий их код должен перехватывать исключение InterruptedException, которое они бросают при прерывании во время ожидания.
Пример, когда мы расцепляли потоки, запускали их и объединяли в конце-концов, тобы получить резалт
SumRunnable2[] s = new SumRunnable2[] { new SumRunnable2(arr, 0, 25), new SumRunnable2(arr, 25, 50), new SumRunnable2(arr, 50, 75), new SumRunnable2(arr, 75, 100), };
Thread[] threads = new Thread[s.length]; for (int i = 0; i < s.length; i++) { Thread thread = new Thread(s[i]); thread.start();
threads[i] = thread; }
for (Thread thread : threads) { thread.join(); //awaiting now till all threads in threads[] finish their work //s.join(); //нету физически! }
long result = 0; for (SumRunnable2 sumRunnable2 : s) { result += sumRunnable2.getResult(); }
Какие способы синхронизации существуют в java?
Синхронизация - это процесс (механизм), который обеспечивает, что доступ к ресурсу (объект, методыи тп) будет доступен только одному потоку одновременно.
В Java используется понятие монитор. Если один поток захватил монитор, все остальные потоки, ждут, пока монитор освободится (состояние BLOCKED).
Любой объект в Java может быть использован как монитор. Для использования синхронизации предусмотрено ключевое слово synchronized
Мы можем использовать synchronized как целиком на метод или если нет необходимости, синхронизировать весь метод, применить только на блок кода
— Если коротко:
Для обеспечения синхронизации в Java реализован прием , называемый
монитором и ключевое слово synchronized
Что такое монитор?
Монитор — механизм взаимодействия и синхронизации процессов, обеспечивающий доступ к неразделяемым ресурсам. Подход к
синхронизации двух или более компьютерных задач, использующих общий ресурс, обычно аппаратуру или набор переменных.
Монитор - это механизм управления, хранящий только один поток исполнения. Как только поток исполнения войдет в монитор, все другие потоки исполнения должны ожидать до тех пор, пока тот не покинет монитор. Монитор служит для защиты общих ресурсов от одновременного использования несколькими
потоками исполнения.
Для монитора в Java отсутствует отдельный класс вроде Monitor. Вместо этого
у каждого объекта имеется свой неявный монитор, вход в который осуществляется автоматически, когда для этого объекта вызывается синхронизированный метод.
Расскажите про методы работы с ключевым словом synchronized?
Для использования синхронизации
предусмотрено ключевое слово synchronized
Мы можем использовать synchronized как целиком на метод или если нет необходимости, синхронизировать весь метод, применить только на блок кода
Объект: Object object = new Object(); synchronized (object) { }
2-3: ключевое слово ставится перед возвращаемым значением!
Метод объекта (в данном случае монитором будет объект, у которого вызывается метод inc):
synchronized void inc() {
state++;
}
Статический метод (в данном случае монитором будет класс статического метода NoSyncIncrementThread.class):
synchronized static void inc() {
state++;
}
Для чего используется ключевое слово volatile?
Иногда, несколько потоков могут использовать одну
переменную.
Для эффективной работы каждый поток копирует себе
значение этой переменной в кеше ядра, на котором
исполняется. Если, при этом, значение переменной
изменится в одном потоке, то другие потоки будут
продолжать работать уже с неактуальным значением
переменной.
Для таких случаев, есть ключевое слово volatile, которым
помечается такая переменная. В таком случае, значение
этой переменной, не будет кэшироваться, а будет всегда
читаться из оперативной памяти.
Его необходимо использовать для переменных, которые используются разными потоками. Это связано с тем, что значение переменной, объявленной без volatile, может кэшироваться отдельно для каждого потока, и значение из этого кэша может различаться для каждого из них. Объявление переменной с ключевым словом volatile отключает для неё такое кэширование и все запросы к переменной будут направляться непосредственно в память.
private volatile boolean flag = true; в задаче ? Спросить!
Как работают методы wait, notify, notifyAll из класса Object?
Взаимодействие потоков
public final void wait() throws InterruptedException
вызывающий поток уступает монитор и переходит в состояние ожидание (WAITING) до тех пор, пока какой-нибудь другой поток не возьмет тот же
монитор и не вызовет метод notify()/notifyAll()
public final native void notify();
возобновляет исполнения одного потока, из которого вызван метод wait() для этого же монитора. Т.е. поток выходит из состояние WAITING и
пытается взять монитор, если получается, то переходит в состояние RUNNABLE, иначе - BLOCKED (пытается захватить монитор)
public final native void notifyAll();
возобновляет выполнение всех потоков, из которых вызван метод wait() для этого же самого монитора. Так же как и с notify(), только для всех
потоков.
Все эти методы объявлены в классе Object, что означает, что методы доступны у всех объектов.
В очень редких случаях, потоки в состоянии WAITING могут проснуться из-за так называемой ложной активизации (spurious wakeup).
Поэтому, вызов метода wait(), должен быть в цикле по условию, которому поток должен засыпать.
wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания (блокирует выполнение метода) до тех пор, пока другой поток не вызовет метод notify() для этого монитора
notify(): продолжает работу потока в рамках одного монитора, у которого ранее был вызван метод wait()
notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait()
Все эти методы вызываются только из синхронизированного контекста - синхронизированного блока или метода.
Чем отличается работа метода wait с параметром и без?
Один метод wait() бесконечно ждет другой поток, пока не будет вызван метод notify() или notifyAll() на объекте. Другие две вариации метода wait() ставят текущий поток в ожидание на определенное время. По истечении этого времени поток просыпается и продолжает работу.
Чем отличаются два интерфейса Runnable и Callable?
Runnable - это основной интерфейс, предоставленный для представления многопоточных задач, а Callable - улучшенная версия Runnable , добавленная в Java 1.5.
Оба интерфейса предназначены для представления задачи, которая может выполняться несколькими потоками. Задачи Runnable можно запускать с помощью класса Thread или ExecutorService , тогда как Callable можно запускать только с использованием ExecutorService.
Возвращаемые значения:
Runnable:
является функциональным интерфейсом и имеет единственный метод run () , который не принимает никаких параметров и не возвращает никаких значений.
Callable:
это универсальный интерфейс, содержащий единственный метод call () , который возвращает универсальное значение V - Future-объект
———————————————————————————-
Обработка исключений:
Runnable:
Поскольку в сигнатуре метода не указано условие throws нет способа распространять дальнейшие проверенные исключения.
—-
+ каллейбл связан с экзекьютором и работать с ним легче.
Callable:
Метод call () Callable содержит предложение «throws Exception» (которые собираются в Future-объекте и которые можено потом доставть используя .get.IntValue, например), поэтому мы можем легко пробрасывать исключения дальше - и исключение checked!
В каких состояниях может быть поток в java?
Класс java.lang.Thread содержит перечисление static State – , которое определяет его потенциальные состояния. В любой момент времени поток может находиться только в одном из следующих состояний:
NEW – вновь созданный поток, который еще не начал выполнение (он остается в этом состоянии, пока мы не запустим его с использованием метода start ())
RUNNABLE – либо запущен, либо готов к выполнению, но ожидает от планировщика потоков выделения времени работы с ЦП
BLOCKED – Он переходит в это состояние, когда ожидает блокировки монитора и пытается получить доступ к фрагменту кода, заблокированному другим потоком.
WAITING – поток находится в состоянии WAITING , когда он ожидает, пока какой-либо другой поток выполнит определенное действие.
[Согласно JavaDocs], любой поток может войти в это состояние, вызвав любой из следующих трех методов:
object.wait ()
thread.join () или
LockSupport.park ()
TIMED WAITING – поток находится в состоянии TIMED WAITING, когда ожидает, пока другой поток выполнит определенное действие в течение установленного промежутка времени. Cуществует пять способов поместить поток в состояние TIMED WAITING :
thread.sleep (long millis)
wait (int timeout) или wait (int timeout, int nanos)
thread.join (long millis)
LockSupport.parkNanos
LockSupport.parkUntil
TERMINATED – завершил выполнение
Что значит приоритет потока?
Приоритеты потоков
Каждый поток в системе имеет свой приоритет. Приоритет – это некоторое число в объекте потока, более высокое значение которого означает больший приоритет. Система в первую очередь выполняет потоки с большим приоритетом, а потоки с меньшим приоритетом получают процессорное время только тогда, когда их более привилегированные собратья простаивают.
Работать с приоритетами потока можно с помощью двух функций:
void setPriority(int priority) – устанавливает приоритет потока. Возможные значения priority — MIN_PRIORITY, NORM_PRIORITY и MAX_PRIORITY.
int getPriority() – получает приоритет потока.