sonyps4.ru

Что такое многопоточность. Работа с потоками в java

Java — это высокоуровневый язык программирования, разработанный Sun Microsystems. Первоначально создавался для разработки программ для телеприставок и карманных устройств, но позже стал популярным и востребованным языком для создания веб-приложений. Oracle приобрела Sun Microsystems в январе 2010 года, поэтому Java теперь поддерживается и распространяется Oracle.

Java применяет модель объектно-ориентированного программирования и может использоваться для создания приложений, которые могут запускаться на одном компьютере или распределяться между серверами и клиентами в сети. Его также можно использовать для создания небольшого модуля приложения или апплета для использования в качестве части веб-страницы.

Преимущества

Синтаксис Java похож на C ++, но является строго объектно-ориентированным языком программирования. Например, большинство программ Java содержат классы, которые используются для определения объектов и методов. Методы, в свою очередь, назначаются отдельным классам. Java также известен как более строгая формальная знаковая система, чем C ++. Это означает, что переменные и функции должны быть явно определены, что исходный код может выявлять ошибки или «исключения» легче и быстрее, чем другие языки. Это также ограничивает другие типы ошибок, которые могут быть вызваны неопределенными переменными или неназначенными типами.

В отличие от исполняемых файлов Windows (.EXE-файлов) или приложений Macintosh (файлы.APP), программы Java не запускаются непосредственно операционной системой. Вместо этого данные интерпретируются виртуальной машиной Java или JVM, которая работает на нескольких платформах. Все Java-программы являются мультиплатформенными и могут работать на разных платформах, включая компьютеры Macintosh, Windows и Unix. JVM должен быть установлен для приложений или апплетов для запуска. Доступен в виде бесплатной загрузки.

Элементы и принципы

Успеху и популярности Java способствовали характеристики языка. Перечислим основные из них:

    Программы, созданные на Java, обладают качеством переносимости в сети. Исходный код скомпилирован так, что язык программирования вызывает байт-код, который можно запускать в любом месте сети на сервере или клиенте с виртуальной машиной Java (JVM). JVM интерпретирует байт-код в коде, который будет работать на компьютерном оборудовании. Напротив, большинство языков программирования, таких как COBOL, C ++, Visual Basic или Smalltalk, компилируют код в двоичный файл. Бинарные файлы зависят от платформы, поэтому программа, написанная для компьютера под управлением Windows на базе Intel, не может запускать Mac, Linux-машину или мэйнфрейм IBM. JVM включает в себя необязательный компилятор Just-in-time (JIT), который динамически компилирует байт-код в исполняемый код в качестве альтернативы интерпретации одной инструкции байт-кода за раз. Во многих случаях динамическая компиляция JIT быстрее, чем интерпретация виртуальной машины.

    Надежность кода. В отличие от программ, написанных на C ++ и некоторых других языках, объекты Java не содержат ссылок на внешние данные или другие известные объекты. Это гарантирует, что инструкция не содержит адрес хранения данных в другом приложении или в самой операционной системе, из-за чего программа и сама операционная система могут завершиться или инициировать сбой в работе. JVM выполняет ряд проверок на каждом объекте для обеспечения целостности.

    Java — объектно-ориентированный язык. Объект может воспользоваться тем, что он является частью класса объектов и наследует код, который является общим для класса. Данный метод можно рассматривать как одну из возможностей или поведения объекта. Объектно-ориентированный метод относительно распространен в современном ландшафте программирования, но еще в 1996 году лишь несколько языков эффективно применяли объектно-ориентированные концепции и шаблоны проектирования.

    Гибкость апплетов. Java-апплет имеет все характеристики, предназначенные для быстрого запуска.

    Java прост для изучения. Синтаксис языка аналогичен C ++. Если разработчик владеет языкам С/С++, освоить данный язык не составит труда.

    Распространенным заблуждением является то, что существует связь между Java и JavaScript. Эти языки имеют сходство в синтаксисе, но с конструктивной точки зрения являются разными структурами.

Платформы

Существуют три ключевые платформы, на которых программисты разрабатывают Java-приложения:

    Java SE — простые автономные приложения разрабатываются с использованием Java Standard Edition. Ранее известный как J2SE, Java SE предоставляет все API, необходимые для разработки традиционных настольных приложений.

    Java EE — Java Enterprise Edition, ранее известная как J2EE, обеспечивает возможность создания серверных компонентов, которые могут отвечать на веб-цикл запроса-ответа. Такая компоновка позволяет создавать Java-программы, которые могут взаимодействовать с интернет-клиентами, включая веб-браузеры, клиенты на базе CORBA и даже веб-сервисы на основе REST и SOAP.

    Java ME — Java также предоставляет легкую платформу для известную как Java Micro Edition, ранее известная как J2ME. Java ME зарекомендовала себя очень популярной платформой для разработки встроенных устройств, но она изо всех сил пыталась добиться успеха на арене разработки смартфонов. С точки зрения развития смартфонов, Android стал платформой мобильной разработки.

Примеры использования

Используя различные компоненты, предоставляемые Java EE, разработчикам легко писать программы, в которых используются популярные шаблоны проектирования программного обеспечения и универсально согласованные передовые методы.

Фреймворки Struts, Spring и JavaServer Faces используют сервлет Java для реализации шаблона проектирования переднего контроллера для централизации запросов.

Между тем большая часть экосистемы Java — это огромное количество проектов с открытым исходным кодом, программных платформ и API, которые создало сообщество с использованием языка. Например, в Apache Foundation размещаются различные проекты, написанные с использованием Java, в том числе:

    Простые рамки ведения журналов для Java (SLF4J).

    Большие платформы обработки данных.

    Интеграционные платформы, такие как Apache Camel, Apache Axis и CXF для разработки веб-сервисов RESTful.

    Платформы разработки микросервисов.

Другие предприятия попытаются перевести среды Java EE в облако. Поскольку разработчики создают облачные сервисы, возможность быстрого масштабирования этих сервисов является ключевой проблемой, равно как и возможность совместной работы в облаке.

Критика

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

Другой недостаток заключается в том, что Java-программы не могут линейно масштабироваться в мире высокопроизводительных вычислений. Из-за того что Java ссылается на объекты внутри, сложные и параллельные операции на основе списка могут привести JVM к поиску обходных решений. Язык Scala решает многие из недостатков языка Java, которые уменьшают его способность масштабирования.

Потоки Java

Потоки — это парадигма программирования, изобретенная Дж. Полом Родкер-Моррисоном в конце 60-х годов, в которой используется понятие «обработка данных» для проектирования и создания приложений. Поток Java определяет приложения как сети процессов, которые обмениваются данными с помощью блоков данных (информационных пакетов), перемещающихся по предопределенным соединениям. Эти процессы можно подключать бесконечно, чтобы сформировать приложения, не изменяя их внутреннее содержимое. Таким образом поток ориентируется на компонент.

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

Процессы взаимодействуют через соединения, к которым обращаются через порты. Связи обычно реализуются через ограниченные буферы. Размер буфера или максимальное количество пакетов, которые может содержать очередь, называется пропускной способностью подключения. Некоторые реализации позволяют устанавливать соединения с емкостью 0, что означает, что IP-адреса данных немедленно передаются между процессами отправки и получения.

Определение

Потоковое программирование рассматривает приложение как набор процессов («черных ящиков»), связывающихся через соединения, к которым процессы обращаются с помощью портов. Процесс является экземпляром компонента, работающим одновременно с другими процессами, включая другие экземпляры одного и того же компонента.

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

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

Основными характеристиками являются повторное использование кода, тестируемость и параллельность.

Введение

Потоковое программирование определяет приложения с использованием метафоры «фабрика данных». Приложение рассматривается не как единый последовательный процесс, который начинается в определенный момент времени, а затем делает одну операцию в единицу времени до полного завершения, а как сеть асинхронных процессов, сообщающихся посредством потоков структурированных блоков данных, называемых информационными пакетами (IP). Сеть определяется внешними процессами как список соединений, которые интерпретируются частью программного обеспечения, обычно называемой планировщиком.

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

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

Сеть

Определение сети обычно схематично и преобразуется в список соединений на каком-либо языке или нотации более низкого уровня. Потоки ввода/вывода Java часто являются языком визуального программирования на этом уровне. Более сложные определения сетей имеют иерархическую структуру, которая создается из подсетей с гибкими соединительными связями. Другие примеры потоков Java основаны на более традиционных языках программирования. В частности RaftLib использует C ++ и iostream-подобные операторы для выполнения потока Java.

Описание процесса

Работа с потоками Java демонстрирует связывание данных как наиболее слабое соединение между компонентами. Концепция свободной связи, в свою очередь, связана с концепцией сервис-ориентированных архитектур. Пул потоков Java соответствует ряду критериев для такой архитектуры.

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

Характеристики

Потоковое программирование обладает следующими уникальными свойствами:

    Структурность: диаграммы и компоненты имеют четкую структуру (интерфейс, состояние и поведение).

    Дизайн системы разделен на два слоя: графический (обычно визуальный) и компонентный (обычно текстовый). С точки зрения поощряются различные роли. В качестве примера: потоки Java управляются ролевыми функциями «Дизайнер графиков» и «Компонент-реализатор».

    Принцип параллелизма: каждый процесс выполняется в своем потоке, сопутствующей программе или другой среде.

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

    Информационные пакеты при создании потока Java имеют жизненный цикл и принадлежат только одному процессу.

    Компоненты могут иметь несколько входов или выходов.

    Приложение представляет собой график, а не дерево. Разрешены циклические соединения (петли обратной связи).

    Соединения могут быть объединены в график, подразумевая, что пакеты из разных дуг поступают на входной порт. Соединения должны быть разделены через компонент из-за разнообразия стратегий разделения и правила владения IP.

    Соединения реализованы как ограниченные буферы с объемом от 0 до числа, ограниченного реализацией.

Примеры

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

«Слова» явно указаны в описании проблемы, поэтому разработчику необходимо рассматривать их как информационные пакеты (IP). В потоках ввода Java нет единой иерархии вызовов, поэтому у программиста нет необходимости настраивать приоритезацию.

При создании потока Java исходные информационные пакеты (IIP) могут использоваться для указания параметрической информации, такой как желаемая длина выходной записи или имена файлов. IIP - это блоки данных, связанные с портом в определении сети, которые становятся «обычными» IP-адресами, когда «получаемый» параметр выдается для соответствующего порта.

Пакетное обновление

Этот тип программы включает передачу файла сведений (изменений, добавлений и удалений) на главный файл и создание обновленного основного файла и одного или нескольких отчетов. Новый поток Java довольно сложно кодировать с использованием синхронного процедурного кода, поскольку два (иногда более) потока ввода должны синхронизироваться.

В потоковой архитектуре компонент многократного использования значительно упрощает запись этого типа приложения, поскольку данный инструмент объединяет два потока и вставляет данные IP, чтобы указать уровни группировки. Предположим, что один или два потока Java состоят из IP-адресов с ключевыми значениями 1, 2 и 3, а IP-адреса второго потока имеет ключевые значения 11, 12, 21, 31, 32, 33 и 41, где первая цифра соответствует значениям главного ключа. Используя символы скобок для представления IP-адресов, собранный выходной поток будет выглядеть следующим образом: (m1 d11 d12) (m2 d21) (м3 d31 d32d33) (d41).

Процессы мультиплексирования

Потоковое программирование поддерживает мультиплексирование процессов. Поскольку компоненты доступны только для чтения, любое количество экземпляров данного компонента («процессы») может выполняться асинхронно друг с другом.

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

DrawFBP как инструмент

Потоковое программирование является парадигмой, где программы создаются из компонентов, которые имеют набор определенных входных и выходных портов. Эти порты соединяются вместе, чтобы составить график, определяющий логику и поток данных. DrawFBP — это настольное приложение, которое может определять графики программ на основе потоков для Java и C #. Это самый функционально полный пользовательский интерфейс потокового программирования, но он несколько ограничен возможностями межсетевого экрана Java и невозможностью интроспекции информации, доступной через интерфейс загрузки компонентов.

Ключевой частью пользовательского интерфейса является упрощение создания и понимания программ на основе метода потоков.

Последнее обновление: 27.04.2018

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

Класс Thread

В Java функциональность отдельного потока заключается в классе Thread . И чтобы создать новый поток, нам надо создать объект этого класса. Но все потоки не создаются сами по себе. Когда запускается программа, начинает работать главный поток этой программы. От этого главного потока порождаются все остальные дочерние потоки.

С помощью статического метода Thread.currentThread() мы можем получить текущий поток выполнения:

Public static void main(String args) { Thread t = Thread.currentThread(); // получаем главный поток System.out.println(t.getName()); // main }

По умолчанию именем главного потока будет main .

Для управления потоком класс Thread предоставляет еще ряд методов. Наиболее используемые из них:

    getName() : возвращает имя потока

    setName(String name) : устанавливает имя потока

    getPriority() : возвращает приоритет потока

    setPriority(int proirity) : устанавливает приоритет потока. Приоритет является одним из ключевых факторов для выбора системой потока из кучи потоков для выполнения. В этот метод в качестве параметра передается числовое значение приоритета - от 1 до 10. По умолчанию главному потоку выставляется средний приоритет - 5.

    isAlive() : возвращает true, если поток активен

    isInterrupted() : возвращает true, если поток был прерван

    join() : ожидает завершение потока

    run() : определяет точку входа в поток

    sleep() : приостанавливает поток на заданное количество миллисекунд

    start() : запускает поток, вызывая его метод run()

Мы можем вывести всю информацию о потоке:

Public static void main(String args) { Thread t = Thread.currentThread(); // получаем главный поток System.out.println(t); // main }

Консольный вывод:

Thread

Первое main будет представлять имя потока (что можно получить через t.getName()), второе значение 5 предоставляет приоритет потока (также можно получить через t.getPriority()), и последнее main представляет имя группы потоков, к которому относится текущий - по умолчанию также main (также можно получить через t.getThreadGroup().getName())

Недостатки при использовании потоков

Далее мы рассмотрим, как создавать и использовать потоки. Это довольно легко. Однако при создании многопоточного приложения нам следует учитывать ряд обстоятельств, которые негативно могут сказаться на работе приложения.

На некоторых платформах запуск новых потоков может замедлить работу приложения. Что может иметь большое значение, если нам критичная производительность приложения.

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

В этой статье рассказывается, как реализовать потоки, используя язык программирования Java. Перед тем как рассмотреть детали программирования на Java, давайте приступим к обзору о потоках в общем.

Если сказать просто, то поток(thread) – это путь программного выполнения. Большинство программ, написанных сегодня, запускаются одним потоком, проблемы начинают возникать, когда несколько событий или действий должны произойти в одно время. Допустим, например, программа не способна рисовать картинку пока выполняет чтение нажатия клавиш. Программа должна уделять всё своё внимание клавиатуре, вследствие чего отсутствует возможность обрабатывать более одного события одновременно. Идеальным решением для этой проблемы может служить возможность выполнения двух или более разделов программы в одно время. Потоки позволяют нам это сделать.

Многопоточные приложения предоставляют мощь при запуске многих потоков в рамках одной программы. С логической точки зрения, многопоточность означает, что несколько строк из одной и той же программы могут быть выполнены в одно и то же время, однако, это не то же самое, что запустить программу дважды и сказать, что несколько строк кода выполняются в одно время. В этом случае, операционная система обрабатывает две программы раздельно и как отдельные процессы. В Unix, разветвляющий(forking) процесс создаёт дочерний процесс с разным адресным пространством для кода и данных. Вместе с тем, fork() создаёт много накладок для операционной системы, это влечёт за собой интенсивную нагрузку на процессор. При запуске потока, эффективный путь выполнения создаётся за счёт распределения исходного пространство данных родителя. Идея совместного использования данных очень выгодна, но вызывает некоторые вопросы, которые мы обсудим позже.

Создание потоков

Создатели Java милостиво предоставили две возможности создания потоков: реализовать(implementing) интерфейс и расширить(extending) класс. Расширение класса это путь наследования методов и переменных класса родителя. В этом случае можно наследоваться только от одного родительского класса. Это ограничение внутри Java можно побороть реализацией интерфейса, который является наиболее распространённым способом создания потоков. (Заметим, что способ наследования позволяет только запустить класс как поток. Это позволяет классу только выполнить start() и т.п.).

Интерфейсы дают возможность программистам заложить фундамент одного класса. Они используются для разработки требований к набору классов для реализации. Интерфейс устанавливает правила. Разные наборы классов, которые реализуют интерфейс, должны следовать этим правилам.

Есть некоторые отличия между классом и интерфейсом. Во-первых, интерфейс может только содержать абстрактные методы и/или static final переменные (константы). Классы, с другой стороны, могут реализовывать методы и содержать переменные, которые не выступают в качестве констант. Во-вторых, интерфейс не может реализовывать никаких методов. Класс, который реализовывает интерфейс, должен реализовать все методы, которые описаны в интерфейсе. У интерфейса есть возможность расширяться за счёт других интерфейсов, и (в отличие от классов) могут расширяться от нескольких интерфейсов. К тому же, экземпляр интерфейса не может быть создан, используя, оператор new; например, Runnable a = new Runnable(); не разрешается .

Для первого способа создания потока необходимо просто наследоваться от класса Thread. Делайте так, только если классу нужно только выполниться как отдельному потоку и никогда не понадобиться наследоваться от другого класса. Класс Thread определён в пакете java.lang, который необходимо импортировать, что бы наши классы знали о его описании:


import java.lang.*;
public class Counter extends Thread {
public void run() {
....
}
}

Пример выше создаёт новый класс Counter, который расширяет класс Thread и подменяет метод Thread.run() для своей реализации. В методе run() происходит вся работа класса Counter как потока. Такой же класс можно создать, реализуя интерфейс Runnable:


import java.lang.*;
public class Counter implements Runnable {
Thread T;
public void run() {
....
}
}

Здесь осуществляется абстрактный метод run(), который описан в интерфейсе Runnable. Отметим, что у нас есть экземпляр класса Thread, переменная класса Counter. Единственное отличие этих двух методов заключается в том, что реализация Runnable является более гибкой для создания класса Counter. В примере, который описан выше, есть возможность расширения класса Counter, если в этом есть такая необходимость. Большинство классов, которые должны выполняться как потоки, реализуют Runnable, поскольку они, вероятно, могут расширить свою функциональность за счёт другого класса.

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


package java.lang;
public interface Runnable {
public abstract void run();
}

Это и всё, что есть в интерфейсе Runnable. Интерфейс – это всего лишь описание, которое классы должны реализовать. Итак, Runnable, заставляет только запустить метод run(). Вследствие этого, большая часть работы полагается на класс Thread. Более пристальный взгляд на класс Thread даст представление о том, что на самом деле происходит:


public class Thread implements Runnable {
...
public void run() {
if (target != null) {
target.run();
}
}
...
}

Во фрагменте кода, который представлен выше видно, что класс Thread, так же реализует интерфейс Runnable. Thread.run() выполняет проверку, что бы удостовериться в том, что этот класс (класс, который выполняется как поток) не равен null, и только потом выполняет метод run(). Когда это произойдёт, то метод run() запустит поток.

Запуск и остановка

Различные способы создания объектов потока сейчас очевидны, мы продолжим дискуссию о реализации потоков, начиная с путей запуска и остановки их, используя, маленький applet, который содержит поток для иллюстрации этих механизмов:

Апплет выше, начнёт отсчёт с нуля и будет отображать это и на экране, и в консоль. На первый взгляд может сложиться впечатление, что программа начинает отсчёт и показывает каждую цифру, но это не так. При более внимательном рассмотрении этого апплета будет выявлено, как на самом деле работает этот апплет.

В этом случае, класс CounterThread был вынужден реализовать интерфейс Runnnable, что бы дальше была возможность расширить класс Applet. Все апплеты начинают свою работу с метода init(), переменная Cout инициализируется нулём и создаётся новый объект класса Thread. Передавая this в конструктор класса Thread, таким образом, новый поток будет знать какой объект запускается. В этом случает this это ссылка на CounterThread. После того как поток создан его нужно запустить. Вызываем метод start(), который в свою очередь вызывает метод run() объекта CounterThread, то есть CounterThread.run(). Сразу выполниться метод start() и в это же время начнёт свою работу поток. Заметим, что в методе run() бесконечный цикл. Он бесконечен, потому что, как только выполниться метод run(), то поток закончит работу. Метод run() будет инкрементировать переменную Count, ожидать(sleep) 10 секунд и посылать запрос на обновление экрана апплета.

Заметим, что вызова метода sleep именно в потоке является очень важным. Если это не так, то программа займёт всё процессорное время для своего процесса и не даст возможности любым другим методам, например методам, выполниться. Другой способ остановить выполнение потока это вызвать метод stop(). В данном примере, поток останавливается, когда происходит нажатие мыши в пределах апплета. В зависимости от скорости компьютера, на котором запущен апплет, не все числа будут отображены, потому что инкрементирование происходит независимо от прорисовки апплета. Апплет может не обновляться после каждого запроса на прорисовку, так как ОС может поставить запрос в очередь запросов и последующие запросы на обновление будут удовлетворены с одним запросом. Пока запросы на перерисовку собираются в очередь, переменная Count продолжает увеличиваться, но не отображается.

Приостановка и возобновление

Когда поток остановлен с использованием метода stop() он уже не может быть возобновлён с использованием метода start(), сразу после вызова метода stop() происходит уничтожение выполняющегося потока. Вместо этого вы можете приостановить выполнение потока, используя метод sleep() на определённый отрезок времени и потом выполнение потока продолжится, когда выйдет время. Но это не самое лучшее решение, если поток необходимо запустить, когда произойдёт определённое условие. Для этого, используется метод suspend(), который даёт возможность временно прекратить выполнение потока и метод resume(), который позволяет продолжить выполнение потока. Следующий апплет является изменением апплета, который был дан выше, но с использованием методов suspend() и resume():


public class CounterThread2 extends Applet implements Runnable
{
Thread t;
int Count;
boolean suspended;
public boolean mouseDown(Event e,int x, int y)
{
if(suspended)
t.resume();
else
t.suspend();
suspended = !suspended;
return true;
}
...
}

Для того чтобы сохранить текущее состояние апплета используется логическая(boolean) переменная suspended. Характеристики разных состояний апплета является важной частью, потому что некоторые методы могут выкидывать исключения, если они вызываются не из того состояния. Например, если поток запущен и остановлен, вызов метода start() приведёт к исключению IllegalThreadStateException .

Планирование

В Java есть Планировщик Потоков(Thread Scheduler), который контролирует все запущенные потоки во всех программах и решает, какие потоки должны быть запущены, и какая строка кода выполняться. Существует две характеристики потока, по которым планировщик идентифицирует процесс. Первая, более важная, это приоритет потока, другая, является-ли поток демоном(daemon flag). Простейшее правило планировщика, это если запущены только daemon потоки, то Java Virtual Machine (JVM) вызгрузиться. Новые потоки наследуют приоритет и daemon flag от потока, который его создал. Планировщик определяет какой поток должен быть запущен, анализируя приоритет всех потоков. Потоку с наивысшим приоритетом позволяется выполниться раньше, нежели потокам с более низкими приоритетами.

Планировщик может быть двух видов: с преимуществом и без. Планировщик с преимуществом предоставляет определённый отрезок времени для всех потоков, которые запущены в системе. Планировщик решает, какой поток следующий запуститься или возобновить работу через некоторый постоянный период времени. Когда поток запуститься, через этот определённый промежуток времени, то выполняющийся поток будет приостановлен и следующий поток возобновит свою работу. Планировщик без приоритета решает, какой поток должен запуститься и выполняться до того, пока не закончит свою работу. Поток имеет полный контроль над системой настолько долго, сколько ему захочется. Метод yield() можно использовать для того чтобы принудить планировщик выполнить другой поток, который ожидает своей очереди. В зависимости от системы, на которой запущена Java, планировщик может быть либо с преимуществом, либо без него.

Приоритеты

Планировщик определяет, какой поток должен запуститься, основываясь на номер приоритета, назначенный каждому потоку. Приоритет потока может принимать значения от 1 до 10. По умолчанию, значение приоритета для потока является Thread.NORM_PRIORITY, которому соответствует значение 5. Так же доступны две других static переменных: Thread.MIN_PRIORITY, значение 1, и Thread.MAX_PRIORITY – 10. Метод getPriority() может использоваться для получения текущего значения приоритета соответствующего потока.

Daemon потоки

Такие потоки иногда ещё называются “службами”, которые обычно запускаются с наименьшим приоритетом и обеспечивают основные услуги для программы или программ, когда деятельность компьютера понижается. Примером такого потока может служить сборщик мусора. Этот поток, предусмотрен JVM, сканирует программы на наличие переменных, к которым больше никогда не придется обращаться, и освобождает их ресурсы, возвращая их системе. Поток может стать daemon потоком, передав булево значение true в метод setDaemon(). Если принято значение false, то поток становится обычным пользовательским потоком. Тем не менее, это необходимо сделать до того как поток запустится.

Пример планирования

Представленный апплет демонстрирует выполнение двух потоков с разными приоритетами. Один поток запущен с наименьшим приоритетом, а другой с наивысшим. Потоки увеличивают значение счетчика, пока счетчик потока с большим приоритетом не догонит счетчик потока с меньшим приоритетом.

Заключение

Использование потоков в Java позволит программистам более гибко использовать преимущества Java в их программах. Простота создания, настраивания и запуска потоков даст Java программистам возможность разрабатывать переносимые и мощные апплеты/приложения, которые невозможно выполнить в других языках третьего поколения. Потоки позволяют любым программам выполнять несколько задач как одну. В интернет ориентированных(Internet-aware) языках, таких как Java, это очень важный инструмент.

Об авторе

Donald G. Drake программирует на Java начиная с альфа релиза(alpha release). Drake написал апплеты для специальных веб-сайтов. Наиболее популярна его версия TickerTape (http://www.netobjective.com/java/TickerTapeInfo.html), которая может быть широко сконфигурирована для внедрения в любые веб-страницы. Drake имеет степень бакалавра наук в области компьютерных наук в John Carroll University. В настоящее время он получает степень магистра наук в DePaul University.

Прежде чем приступить к разговору о многопоточности, следует уточнить некоторые термины.

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

Процесс

Процесс (process) - это объект, который создается операционной системой, когда пользователь запускает приложение. Процессу выделяется отдельное адресное пространство, причем это пространство физически недоступно для других процессов. Процесс может работать с файлами или с каналами связи локальной или глобальной сети. Когда вы запускаете текстовый процессор или программу калькулятора, вы создаете новый процесс.

Поток

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

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

Приоритеты потоков в приложениях Java

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

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

Распределение времени выполняется по прерываниям системного таймера. Поэтому каждому потоку дается определенный интервал времени, в течении которого он находится в активном состоянии.

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

Каким именно образом?

Приложения Java могут указывать три значения для приоритетов потоков. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY.

По умолчанию вновь созданный поток имеет нормальный приоритет NORM_PRIORITY. Если остальные потоки в системе имеют тот же самый приоритет, то все потоки пользуются процессорным времени на равных правах.

При необходимости вы можете повысить или понизить приоритет отдельных потоков, определив для них значение приоритета, соответственно, MAX_PRIORITY или MIN_PRIORITY. Потоки с повышенным приоритетом выполняются в первую очередь, а с пониженным - только при отсутствии готовых к выполнению потоков, имеющих нормальный или повышенный приоритет.

должны воспользоваться классом java.lang.Thread. В этом классе определены все методы, необходимые для создания потоков, управления их состоянием и синхронизации.

Как пользоваться классом Thread?

Есть две возможности.

  • Во-первых, вы можете создать свой дочерний класс на базе класса Thread. При этом вы должны переопределить метод run. Ваша реализация этого метода будет работать в рамках отдельного потока.
  • Во-вторых, ваш класс может реализовать интерфейс Runnable. При этом в рамках вашего класса необходимо определить метод run, который будет работать как отдельный поток.

Второй способ особенно удобен в тех случаях, когда ваш класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом вам нужна многопоточность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи.

Методы класса Thread

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

С помощью конструкторов вы можете создавать потоки различными способами, указывая при необходимости для них имя и группу. Имя предназначено для идентификации потока и является необязательным атрибутом. Что же касается групп, то они предназначены для организации защиты потоков друг от друга в рамках одного приложения.

Методы класса Thread предоставляют все необходимые возможности для управления потоками, в том числе для их синхронизации.

Поля

Три статических поля предназначены для назначения приоритетов потокам.

  • NORM_PRIORITY

Нормальный

public final static int NORM_PRIORITY;
  • MAX_PRIORITY

Максимальный

public final static int MAX_PRIORITY;
  • MIN_PRIORITY

Минимальный

public final static int MIN_PRIORITY;

Конструкторы

Создание нового объекта Thread

public Thread();

Создвание нового объекта Thread с указанием объекта, для которого будет вызываться метод run

public Thread(Runnable target); public Thread(Runnable target, String name);

Создание объекта Thread с указанием его имени

public Thread(String name);

Создание нового объекта Thread с указанием группы потока и объекта, для которого вызывается метод run

public Thread(ThreadGroup group, Runnable target);

Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

public Thread(ThreadGroup group, Runnable target, String name);

Создание нового объекта Thread с указанием группы потока и имени объекта

public Thread(ThreadGroup group, String name);

Методы

  • activeCount

Текущее количество активных потоков в группе, к которой принадлежит поток

public static int activeCount();
  • checkAccess

Текущему потоку разрешается изменять объект Thread

public void checkAccesss();
  • countStackFrames

Определение количества фреймов в стеке

public int countStackFrames();
  • currentThread

Определение текущего работающего потока

public static Thread currentThread();
  • destroy

Принудительное завершение работы потока

public void destroy();
  • dumpStack

Вывод текущего содержимого стека для отладки

public static void dumpStack();
  • enumerate

Получение всех объектов Tread данной группы

public static int enumerate(Thread tarray);
  • getName

Определение имени потока

public final String getName();
  • getPriority

Определение текущего приоритета потока

public final int getPriority();
  • getThreadGroup

Определение группы, к которой принадлежит поток

public final ThreadGroup getThreadGroup();
  • interrupt

Прерывание потока

public void interrupt();
  • interrupted
public static boolean interrupted();
  • isAlive

Определение, выполняется поток или нет

public final boolean isAlive();
  • isDaemon

Определение, является ли поток демоном

public final boolean isDaemon();
  • isInterrupted

Определение, является ли поток прерванным

public boolean isInterrupted();
  • join

Ожидание завершения потока

public final void join();

Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах

public final void join(long millis);

Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах и наносекундах

public final void join(long millis, int nanos);
  • resume

Запуск временно приостановленного потока

public final void resume();

Метод вызывается в том случае, если поток был создан как объект с интерфейсом Runnable

public void run();
  • setDaemon

Установка для потока режима демона

public final void setDaemon(boolean on);
  • setName

Устаовка имени потока

public final void setName(String name);
  • setPriority

Установка приоритета потока

public final void setPriority(int newPriority);
  • sleep
public static void sleep(long millis);

Задержка потока на заднное время. Время задается в миллисекундах и наносекундах

public static void sleep(long millis, int nanos);
  • start

Запуск потока на выполнение

public void start();
  • stop

Остановка выполнения потока

public final void stop();

Аварийная остановка выполнения потока с заданным исключением

public final void stop(Throwable obj);
  • suspend

Приостановка потока

public final void suspend();
  • toString

Строка, представляющая объект-поток

public String toString();
  • yield

Приостановка текущего потока для того чтобы управление было передано другому потоку

public static void yield();

Создание дочернего класса на базе класса Thread

Рассмотрим первый способ реализации многопоточности, основанный на наследовании от класса Thread. При использовании этого способа вы определяете для потока отдельный класс, например, так:

class DrawRectangles extends Thread { . . . public void run() { . . . } }

Здесь определен класс DrawRectangles, который является дочерним по отношению к классу Thread.

Обратите внимание на метод run. Создавая свой класс на базе класса Thread, вы должны всегда определять этот метод, который и будет выполняться в рамках отдельного потока.

Заметим, что метод run не вызывается напрямую никакими другими методами. Он получает управление при запуске потока методом start.

Как это происходит?

Рассмотрим процедуру запуска потока на примере некоторого класса DrawRectangles.

Вначале ваше приложение должно создать объект класса Thread:

public class MultiTask2 extends Applet { Thread m_DrawRectThread = null; . . . public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } } }

Создание объекта выполняется оператором new в методе start, который получает управление, когда пользователь открывает документ HTML с аплетом. Сразу после создания поток запускается на выполнение, для чего вызывается метод start.

Что касается метода run, то если поток используется для выполнения какой либо периодической работы, то этот метод содержит внутри себя бесконечный цикл. Когда цикл завершается и метод run возвращает управление, поток прекращает свою работу нормальным, не аварийным образом. Для аварийного завершения потока можно использовать метод interrupt.

Остановка работающего потока выполняется методом stop. Обычно остановка всех работающих потоков, созданных аплетом, выполняется методом stop класса аплета:

public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } }

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

Реализация интерфейса Runnable

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

Идея заключается в том, что основной класс аплета, который является дочерним по отношению к классу Applet, дополнительно реализует интерфейс Runnable, как это показано ниже:

public class MultiTask extends Applet implements Runnable { Thread m_MultiTask = null; . . . public void run() { . . . } public void start() { if (m_MultiTask == null) { m_MultiTask = new Thread(this); m_MultiTask.start(); } } public void stop() { if (m_MultiTask != null) { m_MultiTask.stop(); m_MultiTask = null; } } }

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

Для создания потока используется оператор new. Поток создается как объект класса Thread, причем конструктору передается ссылка на класс аплета:

m_MultiTask = new Thread(this);

При этом, когда поток запустится, управление получит метод run, определенный в классе аплета.

Как запустить поток?

Запуск выполняется, как и раньше, методом start. Обычно поток запускается из метода start аплета, когда пользователь отображает страницу сервера Web, содержащую аплет. Остановка потока выполняется методом stop.



Загрузка...