Skip to content

Потоки

b4w edited this page Aug 7, 2016 · 1 revision

Processes and Threads

Процессы

  • Приложение со своим набором run-time ресурсов и собственной памятью.
  • Взаимодействие через Inter Process Communication ресурсы.
  • Можно запускать на нескольких компьютерах.

Потоки

  • "Живут" в одном процессе
  • Используют общую память "Heap" и другие ресурсы приложения
  • Старт приложения - создание main потока
  • Потоки могут пораждать другие потоки и взаимодействовать с ними

Поток

  • Объект, у класса которого есть методы start() и run().
  • После вызова метода start() будет выполнен run().
  • Метод run() будет выполнен в своем стеке.

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

Threads

threads

Порядок не определен

threads

Мы создавая потоки не получаем от операционной системы гарантии последовательности исполнения методов run()

Роль операционной системы

Операционная система

  • Создает потоки
  • Переключает потоки (context switch)
  • API для уведомления потока

Interface Runnable

Поток - это объект, реализующий интерфейс 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

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();
	}
}

Runnable vs Thread

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(); // если надо остановить текущий поток до окончания другого потока

Sleep and interrupt

Если нужно остановить выполнение потока 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;
	}
}	

Join

Если надо остановить текущий поток до окончания другого потока

  • В текущем потоке вызываем 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: статья про многопоточность

Executor (Паттерн)

Объект, который выполняет 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

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

TackExtendedRunnable tack = new TackExtendedRunnable(); // сласс, который implement Runnable
Thread t = new Thread(tack);
t.start();
t.join(); // в данном потоке подцепились к тому потоку, который создали и ждем результата.
String value = task.getSavedValue(); // после того как поток t отработал, можем взять результат 

ExecutorService

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

Future - объект, обертка над value, которое будет получено в будущем. Например: мы получили future. В тот момент, когда мы его получили оно не обязательно содержит в себе какое-то значение, но по контракту значение в будущем должно там появиться (результат выполнения задачи в некоторм другом потоке).

Future<Integer> future = pool.submit(callable);
future.get(); // блокировка до получения результата

Future может пригодиться например для такой задачи: Мы можем создать pool большого размера, задач сделать гораздо больше, все их submit. Затем в цикле извлекать значения из future.

Clone this wiki locally