Новые информационные технологии и программное обеспечение
  RSS    


Java - универсальный мультиплатформенный язык программирования

Потоки Java: обзор

Язык Java разрабатывался с встроенной поддержкой многопотоковости. Потоки — один из ключевых элементов технологии Java; они поддерживаются как на языковом (синтаксическом) уровне, так и на уровне виртуальной машины Java и библиотек классов. Во многих случаях потоки Java очень похожи на потоки POSIX (pthreads). Библиотеки классов Java предоставляют класс thread, который поддерживает широкий набор методов для запуска, выполнения и остановки потока, а также проверки его состояния.

Поддержка потоков в Java включает в себя сложный набор примитивов синхронизации на основе мониторов и переменных условия. На уровне языка методы внутри класса или блоки кода, для которых объявляется синхронизация, не выполняются параллельно. Такие методы или блоки выполняются под контролем мониторов, которые помогают убедиться, что данные, доступ к которым осуществляется в этих методах или блоках, остаются в согласованном состоянии. Каждый объект Java имеет собственный монитор, создание экземпляра и активация которого выполняется виртуальной машиной Java в момент первого использования. Мониторы работают примерно так же, как и пара переменной условия и мьютекса, как они определены в pthreads. Однако, в отличие от pthreads, можно прервать поток Java, пока он находится в состоянии ожидания, например, если он ожидает уведомления о событии или заблокирован при вызове ввода-вывода.

Потоки Java: программа 'п'

Этот простой пример показывает, как создать распараллеленный вариант программы 'п' с помощью "чистых" потоков Java.


  1. public class PI1 {   
  2. static long num_steps = 100000;   
  3. static double step;   
  4. static double sum = 0.0;   
  5. static int part_step;   
  6. static class PITask extends Thread {   
  7. int part_number;   
  8. double x = 0.0;   
  9. double sum = 0.0;   
  10. public PITask(int part_number) {   
  11. this.part_number = part_number;   
  12. }   
  13. public void run() {   
  14. for (int i = part_number; i < num_steps; i += part_step)   
  15. {   
  16. x = (i + 0.5) * step;   
  17. sum += 4.0 / (1.0 + x * x);   
  18. }   
  19. }   
  20. }   
  21. public static void main(String[] args) {   
  22. ;   
  23. int i;   
  24. double pi;   
  25. step = 1.0 / (double) num_steps;   
  26. part_step = Runtime.getRuntime().availableProcessors();   
  27. PITask[] part_sums = new PITask[part_step];   
  28. for (i = 0; i < part_step; i++) {   
  29. (part_sums[i] = new PITask(i)).start();   
  30. }   
  31. for (i = 0; i < part_step; i++) {   
  32. try {   
  33. part_sums[i].join();   
  34. catch (InterruptedException e) {   
  35. }   
  36. sum += part_sums[i].sum;   
  37. }   
  38. pi = step * sum;   
  39. System.out.println(pi);   
  40. }   
  41. }   

Чтобы запустить новый поток в Java, обычно нужно создать подкласс класса Thread и определить пользовательский метод run(), выполняющий основную работу, которая должна быть распараллелена. В нашем примере эта задача реализована с помощью метода run() класса PITask. Из соображений производительности весь участок интегрирования был разбит на part_step фрагментов, так чтобы число этих фрагментов было равно числу имеющихся процессоров. Объекты PITask принимают параметр part_number, который помечает данный фрагмент участка интегрирования. Таким образом, в теле run() вычисляется промежуточная сумма по данному фрагменту участка интегрирования. Реальный поток запускается и выполняется параллельно после вызова метода start(). Это делается в цикле для всех фрагментов. Затем начинает работать второй цикл, где с помощью метода join() каждого параллельного потока ожидается завершение выполнения последнего, после чего результаты, полученные от каждого потока, суммируются. В данном примере каждый фрагмент участка интегрирования явно назначается отдельному потоку Java.

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

Параллельная среда Java FJTask

В то время как "чистые" потоки Java, описанные в предыдущем разделе, представляют собой самый нижний уровень поддержки многопотоковости в Java, существует еще множество высокоуровневых библиотек распараллеливания, которые предназначены как для расширения базовых возможностей многопотоковости в Java, так и для добавления решений для некоторых часто встречающихся задач. Ярким примером является пакет java.util.concurrent, который входит в стандарт Java начиная с версии 1.5. Этот пакет включает в себя множество усовершенствований базового распараллеливания Java, таких как поддержка пулов потоков, элементарные переменные и сложные примитивы синхронизации. Однако некоторые компоненты пакета util.concurrent не вошли в стандарт J2SE и в настоящее время доступны в виде отдельной библиотеки под названием EDU.oswego.cs.dl.util.concurrent.

Одним из наиболее важных таких компонентов является среда FJTask, которая реализует концепцию параллелизма "разветвление-объединение" для Java. Эта среда предназначена, в первую очередь, для распараллеливания сложных вычислений, таких как численное интегрирование или перемножение матриц. Задачи FJTask — это облегченные аналоги потоков Thread. Эту технологию часто называют "параллелизмом на основе задач", в отличие от "параллелизма на основе потоков". Задачи FJTask обычно выполняются с помощью того же пула потоков Java. Задачи FJTask поддерживают вариации самых общих методов класса Thread, включая start(), yield() и join(),
и не поддерживают некоторые методы потоков Java, например управление приоритетами. Основной выигрыш при использовании задач FJTask получается за счет того факта, что они не поддерживают блокировку операций любого рода. Ничто не мешает выполнить блокировку внутри FJTask, и очень короткие ожидания и блокировки вполне приемлемы. Задачи FJTask не предназначены для поддержки произвольной синхронизации, поскольку не существует способа приостановить, а затем продолжить выполнение отдельных задач после того, как они были запущены. Кроме того, время выполнения задач FJTask должно быть ограничено, и они не должны содержать бесконечных циклов. Задачи FJTask просто должны выполняться до конца, безо всяких ожиданий и блокировок ввода-вывода. Между задачами FJTask и потоками Thread имеются существенные различия. FJTask могут выполняться на два или три порядка быстрее, чем Thread, по крайней мере если они работают на виртуальных машинах Java с высокопроизводительным сбором мусора (каждая задача FJTask производит очень много мусора) и хорошей встроенной поддержкой потоков.

Оставьте свой комментарий!

Tags:

Добавить комментарий


 

Самое читаемое:

Быстрый поиск

Подписаться в соцсетях

вКонтакте · Twitter · Facebook · Telegram

Инструкции к программам

Инструкции к программам

2020 Новые информационные технологии