-
Notifications
You must be signed in to change notification settings - Fork 36
Потоки
Процессы
- Приложение со своим набором run-time ресурсов и собственной памятью.
- Взаимодействие через Inter Process Communication ресурсы.
- Можно запускать на нескольких компьютерах.
Потоки
- "Живут" в одном процессе
- Используют общую память "Heap" и другие ресурсы приложения
- Старт приложения - создание main потока
- Потоки могут пораждать другие потоки и взаимодействовать с ними
- Объект, у класса которого есть методы start() и run().
- После вызова метода start() будет выполнен run().
- Метод run() будет выполнен в своем стеке.
Вызывая метод start(), мы просим операционную систему создать условия для приложения при которых начнется исполнения последовательности команд метода run(), причем это выполнение пойдет в паралель с выполнением каких-то других методов run() других потоков.
Threads
Порядок не определен
Мы создавая потоки не получаем от операционной системы гарантии последовательности исполнения методов run()
Операционная система
- Создает потоки
- Переключает потоки (context switch)
- API для уведомления потока
Поток - это объект, реализующий интерфейс Runnable. Всего один метод - run();
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String[] args) {
(new Thread(new HelloRunnable())).start();
}
}
class Thread - реализует интерфейс Runnable. Thread содержит метод start() - запуск нового потока.
public static HelloThread extends Thread {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String[] args) {
(new HelloThread()).start();
}
}
Runable
- Можно наследовать класс отличный от Thread
- Runnable класс нужно передавать в конструктор Thread объекта
Thread
- Содержит методы управления потоком
- Текущий Thread можно получить в любом месте кода - Thread thread = Thread.currentThread();
Некоторые методы:
- long getId(); // идентификатор потока
- String getName(); // имя потока
- int getPriority(); // приоритет потока, по которому ОС решает как много ресурсов этому потоку давать (время исполнения)
- void setPriority(int priority); // задание приоритета потока
- static void sleep(long ms); // останавливаем исполнение потока на определенное время (ms). Но при этом нет гарантии, что поток проснется через определенное время ms. Т.е. поток не проснется раньше, но насколько позже - решит ОС.
- void interrupt(); // возможность разбудить поток. Реакция на interrupt() сильно зависит от того, в каком состоянии этот поток находится. Если поток спит, то interrupt() приведет к InterruptedException (поток будет разбужен через вызов исключения). Если поток не спит, то выброса исключения не произойдет, а будет только выстовленно в thrue переменная interrupted (boolean interrupted = thrue) (пример с перекачиванием файлов через браузер в отдельном потоке);
- staic boolean interrupted();
- void join(); // если надо остановить текущий поток до окончания другого потока
Если нужно остановить выполнение потока
Thread.sleep(1000);
- остановит выполнение потока на 1 секунду.
Если нужно прервать выполнение потока
thread.interrupt();
- пошлет прерывание потоку thread
Пример:
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
return; // We've been interrupted
}
for (int i=0; i < input.length; i++) {
heavyTask(inputs[i]);
if (Thread.interrupted()) {
return;
}
}
Если надо остановить текущий поток до окончания другого потока
- В текущем потоке вызываем thread.join();
- Текущий поток ждет пока завершится поток thread
public class HelloThread extends Thread {
public void run() {
System.out.println("1. Hello from a thread!");
}
public static void main(String[] args) {
Thread thread = new HelloThread();
thread.start();
thread.join();
System.out.println("2. Hello from main!");
}
}
Наиболее распростронненные понятия многопоточности:
- Mutex
- Critical section
- Monitor
- Semaphore
- Lock
Critical section
Участок исполняемого кода программы, в котором производится доступ к общему ресурсу (данным или устрйству), который не должен быть одновремено использован более чем одним потоком исполнения. Например чтение из файла - не будет кретической секцией, а запись в файл - будет.
Semaphore
Объект, ограничивающий количество потоков, которые могут войти в заданный участок кода.
init(n); // счетчик = n; enter(); // ждать пока счетчик станет больше 0, после этого уменьшить счетчик на еденицу leave(); // увеличить счетчик на еденицу
Например изначально счетчик = 5, при каждом вызове enter(); уменьшаем значение на 1. При достижении 0, мы не можем вызвать метод enter(); Метод leave(); работает в противоположную сторону, увеличивает значение на 1.
Mutex
Mutual exclusion - простейшие двоичные семафоры, которые могут находиться в одном из двух состояний - отмеченном или неотмеченном. Mutex - основа организации кретических секций.
Lock
Блокировка. Это механизм синхронизации, позволяющий обеспечить исключительный доступ к разделяемому ресурсу между несколькими потоками (механизм использования mutex-a). Пример: мы используя mutex и подходя к некоторому участку кода проверяем можем в него зайти или нет.
Мягкая блокировка - каждый поток пытается получить блокировку перед доступом к соответствующему разделяемому ресурсу.
Обязательная блокировка - попытка несанкционированного доступа к заблокированному ресурсу будет прервана, через создание исключения. Аппаратная поддержка: copmare-and-swap
Monitor Механизм взаимодействия и синхронизации процессов. Высокоуровневая конструкция, которая состоит из mutex-a и массива ожидающих очереди потоков. У монитора должен быть механизм остановки потока и сигнализации о доступности прдолжения работы.
Монитор - это объект, который следит за тем что бы всего один поток из массива мог зайти в кретическую секцию, а все остальные стояли и ждали того момента, когда первый поток выйдет и блокировка будет снята. Затем можно будет из очереди взять следующий поток, уведомить его что он может зайти в кретическую секцию и после этого данный поток сможет выполнить необходимый участок кода.
Hepl из habrahabr: статья про многопоточность
Объект, который выполняет Runnable. Разделяет создание Runnable и способ запуска run();
Два возможных способа запуска executor-ом:
// хорошо подходит для тестов
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
Executor - применение шаблона, разделяющего создание в коде сущности и ее использования. Мы отдельно наследуюемся от Runnable, создаем наследника runnable, в котором мы хотим произвести некие действия. Executor необходим нам для того, что бы запустить на исполнение то, что мы написали в Runnable. Как именно executor будет вызывать runnable, создатель этого runnable не обязан знать. С помощью данного паттерна мы развели задачу создания runnable и то каким образом эту задачу исполнить.
Callable - это Runnable, возвращающее результат. Необходим для тогда, когда нужно не просто запустить выполнение в потоке, а получить результат.
TackExtendedRunnable tack = new TackExtendedRunnable(); // сласс, который implement Runnable
Thread t = new Thread(tack);
t.start();
t.join(); // в данном потоке подцепились к тому потоку, который создали и ждем результата.
String value = task.getSavedValue(); // после того как поток t отработал, можем взять результат
ExecutorService - это пулл потоков (который встроен в библиотеку java). Объект, предоставляющий потоки.
ExecutorService pool = Executors.newFixedThreadPool(1); // создаем pool с 1 потоком
Callable<Integer> callable = new TaskImplementsCallable(); // создаем callable (класс, который implements Collable)
Future<Integer> future = pool.submit(callable); // создаем объект future
future.get();
Future - объект, обертка над value, которое будет получено в будущем. Например: мы получили future. В тот момент, когда мы его получили оно не обязательно содержит в себе какое-то значение, но по контракту значение в будущем должно там появиться (результат выполнения задачи в некоторм другом потоке).
Future<Integer> future = pool.submit(callable);
future.get(); // блокировка до получения результата
Future может пригодиться например для такой задачи: Мы можем создать pool большого размера, задач сделать гораздо больше, все их submit. Затем в цикле извлекать значения из future.