Многопоточность в Java. Лекция 1: общие сведения

30 апреля
Владимир Фролов, Java-разработчик, Никита Сизинцев, Android-разработчик
Многопоточность в Java. Лекция 1: общие сведения

Темную силу чувствую я.
Даешь парсек за три года.

1.1 Введение

Статья не будет рассматривать историю механических вычислительных машин для арифметических вычислений, созданных в XIX веке и ранее. В XX веке появились компьютеры в привычном нам понимании, однако на заре компьютерной эры аналоговые и ламповые машины были большими, малопроизводительными и потребляли много электрической энергии. После открытия полупроводниковых материалов размеры ЭВМ уменьшились, а производительность и энергоэффективность существенно возросли. Затем начали появляться первые микросборки и микросхемы, а в 1965 году был сформулирован известный «закон» Мура, который гласил, что количество транзисторов на одном кристалле будет удваиваться каждые 24 месяца.

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

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

1.2 Краткое описание работы процессора

Рассмотрим на простейшем уровне, как работает процессор. Он состоит из АЛУ (арифметико-логического устройства) — главного элемента процессора, регистров, шин данных и адреса, а также дешифратора команд. 

Изначально АЛУ могло выполнять всего лишь несколько элементарных операций: чтение и запись в память, сложение, сдвиг вправо, сдвиг влево, логические AND, OR, NOT, XOR. Вычитать АЛУ не умело, вычитание осуществлялось путем сложения в дополнительном коде, умножение производилось путем сложения и сдвига влево, деление осуществлялось сдвигом вправо и вычитанием. Позже придумали аппаратные схемы, которые поддерживают эти операции и операции с числами с плавающей запятой. Самый главный вывод, который нужно сделать: даже самые простейшие операции как, например, сложение или вычитание двух чисел производится не за одну команду процессора, а за несколько. Следовательно, операция может быть прервана, и процессор может начать выполнять команды из другой программы. Т. е. операции НЕ АТОМАРНЫЕ, и могут быть прерваны другими командами. 

1.3 Поддержка на уровне операционной системы.

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

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

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

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

1.4 Процессы и потоки

Введем понятия процесса и потока.

Процесс — программа, выполняющаяся в текущий момент времени, и все её элементы: адресное пространствоглобальные переменныерегистры, потоки, открытые файлы и т. д. 

Поток (thread) — наименьшая единица обработки, исполнение которой может быть назначено ядром операционной системы. Или совокупность дискретного процессорного времени, в течение которого выполняются команды, или код, для одной логической части программы.

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

Потоки выполнения отличаются от процессов:

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

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

Заключение

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