Использование легковесных процессов

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

Потоки используются при решении многих задач:

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

При запуске поток получает в свое распоряжение определенную долю ресурсов
процессора и, в дальнейшем, работает уже с ней. Говорят, что поток имеет
“тело”, которое содержит операторы, оно находится в методе run().

Реализация потока

Java предусматривает две возможности реализации тела потока:

  • для класса определяется интерфейс Runnable и используется метод
    run(), а также методы start(), stop();
  • построение класса как потомка класса java.lang.Thread.

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

Метод start() выполняется каждый раз при обращении к странице,
содержащей апплет. Метод проверяет состояние объекта Thread, которая
должна быть описана выше, если значение переменной null, то создается
новый объект типа Thread и вызывается метод start() класса Thread.

Рассмотрим процесс создания нового объекта Thread. Наибольший интерес для нас представляет первый аргумент в вызове конструктора Thread.
Переменная this указывает на текущий апплет. Этот аргумент должен реализовать интерфейс Runnable, после чего он становится адресатом потока.
Второй аргумент определяет имя потока.

Остановка потока

Когда вы покидаете страницу с апплетом, использующим потоки, вызывается
метод stop(), основное назначение которого является присвоение объекту
типа Thread значения null.

Возможен и другой вариант, а именно непосредственный вызов метода stop

класса Thread, но он не всегда приемлем, что связано со следующим: метод
stop может быть вызван в “неудачный” момент выполнения метода run(), в
результате чего поток может не прекратить свое выполнение. При повторном
посещении страницы будет вызван метод start и апплет начнет свое
выполнение.

Выполнение потока

Метод run() является “сердцем” потока, именно он определяет назначение не
только потока, но и, зачастую, всего класса.

Как было показано выше, перед остановкой работы апплета, значение потока
становится равным null. Метод run() будет выполняться, пока поток не
примет значение null. В данном примере апплет перерисовывает сам себя,
затем выполнение потока приостанавливается на 1 секунду (1000 милисекунд).
При вызове repaint() происходит обращение к методу paint(), который
меняет содержимое экрана.

Состояние потока

Новый поток

Данное выражение создает новый пустой поток:

С таким потоком вы можете совершить следующие действия: запустить или
остановить (stop(), start()). Попытка вызвать любой другой метод работы
с потоком приводит к вызову исключения IllegalThreadStateException.

Выполняемый

Метод start() подготавливает все необходимые ресурсы для запуска потока
и передает управление методу run().

Невыполняемый

Поток является невыполняемым, если находится в одном из четырех
состояний:
• вызван метод sleep
• вызван метод suspend
• вызван метод wait
• поток заблокирован вводом/выводом

Поток прекратил свое выполнение на 10 секунд. После непродолжительного
сна поток возобновляет свою работу. Вызов метода resume() для спящего
потока не дает результатов.
Чтобы “разбудить” спящий поток (sleep()) необходим процесс ожидания, для
возобновления работы потока (suspend()) достаточно вызвать метод
resume(). Если поток ожидает установки какой-то переменной, то прервать
это можно при помощи notify или notifyAll. Если блокировка по причине
ожидание окончания ввода/вывода, то необходимо дождаться конца обмена.

Смерть потока

Смерть наступает в двух случаях:
• после выполнения метода stop();
• по естественным причинам, т.е., когда метод run() завершил
выполнение.

Метод isAlive() возвращает true, если поток отработал метод start() и не
выполнил метод stop(). Если же возвращен false, то это либо новый поток,
либо он умер.

 

Распределение приоритета между потоками

В классе java.lang.Thread описаны три идентификатора, определяющие
приоритеты для потоков.
• MIN_PRIORITY
• NORM_PRIORITY
• MAX_PRIORITY
При создании потока ему по умолчанию устанавливается NORM_PRIORITY,
изменить приоритет можно путем использования метода setPriority(int).
Изменение приоритета потока заключается в изменении выделяемого ему
промежутка времени.

Использование нескольких потоков

Рассмотрим пример использования нескольких потоков в рамках одного
приложения.

Таким образом, головной класс создает два новых объекта и запускает их на
выполнение. Два вновь созданных класса являются независимыми полноправными потоками и все действия, описанные в методе run() будут
выполняться как потоковые.

 

Класс java.lang.ThreadGroup

Класс предназначен для объединения потоков в группы, что, в значительной
степени, упрощает работу с потоками и позволяет более гибко управлять их
работой. С группой потоков возможны те же основные операции, что и с
простым потоком:
• запуск;
• останов;
• установка приоритетов;
• и т.д.
К тому же для группы потоков можно определять как родителя, так и
потомков.

 

Методы класса java.lang.Thread

Условно разделим все методы на те, которые возвращают значения, и те,
которые их установливают.
Первая группа:
• activeCount() возвращает текущее число активных потоков в группе;
• currentThread() возвращает ссылку на текущий выполняющийся
поток;
• getName() возвращает имя потока;
• getPriority() возвращает приоритет потока;
• getThreadGroup() возвращает ссылку на группу, к которой
принадлежит поток;
• interrupted() возвращает, является ли поток остановленным;
• isAlive() возвращает, жив ли поток ;
• isDaemon() возвращает, является поток демоном;
• isInterrupted() возвращает, остановлен ли поток.
Вторая группа:
• setDaemon(boolean) делает поток демоном;
• setName(String) устанавливает имя потока;
• setPriority(int) изменение приоритета потока.


Текущее время и дата

Как узнать текущую дату и время в разных часовых поясах.

1

 LocalTime

Из данного примера видно как узнать местное время, используя класс LocateTime.

Этот класс представляет информацию о текущем времени без учета часового пояса, и не включает информацию о дате. Формат представления данных часы-минуты-секунды.

Данный класс является неизменным (immutable) и показывает только время.

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

Карта часовых зон:

  • EST – -05:00
  • HST – -10:00
  • MST – -07:00
  • ACT – Australia/Darwin
  • AET – Australia/Sydney
  • AGT – America/Argentina/Buenos_Aires
  • ART – Africa/Cairo
  • AST – America/Anchorage
  • BET – America/Sao_Paulo
  • BST – Asia/Dhaka
  • CAT – Africa/Harare
  • CNT – America/St_Johns
  • CST – America/Chicago
  • CTT – Asia/Shanghai
  • EAT – Africa/Addis_Ababa
  • ECT – Europe/Paris
  • IET – America/Indiana/Indianapolis
  • IST – Asia/Kolkata
  • JST – Asia/Tokyo
  • MIT – Pacific/Apia
  • NET – Asia/Yerevan
  • NST – Pacific/Auckland
  • PLT – Asia/Karachi
  • PNT – America/Phoenix
  • PRT – America/Puerto_Rico
  • PST – America/Los_Angeles
  • SST – Pacific/Guadalcanal
  • VST – Asia/Ho_Chi_Minh

В результате видим:

LocalDate

java.time.LocalDate класс представляет информацию о текущей дате, не содержит информацию о времени или часовой зоне.

Результат выполнения программы

LocalDateTime

java.time.LocalDateTime представляет комбинацию текущего времени и текущей даты, в формате  2015-09-22T21:58:58.931,  без параметра не содержит информацию о часовой зоне.

 


Полиморфизм, абстрактные классы

В данной статье рассмотрим на примере явление полиморфизма, понятие абстрактного класса и интерфейса

Кратко суть полиморфизма можно выразить фразой: «Один интерфейс, множество реализаций». Полиморфизм реализуется с помощью наследования классов. Класс-родитель содержит методы, которые предназначены для наследования.

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

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

Конкретная реализация метода определена в классе-потомке, поэтому каждое животное по-своему реагирует на вызов метода.

Для того, чтобы увидеть этот механизм в действии, создадим новый родительский класс Animal, реализующий метод getVoice():

Далее напишем несколько дочерних классов, наследующих этот класс и по-разному реализующих метод getVoice(). Первый класс – Cats, коты мяукают.

Второй класс – Dogs, собаки лают.

Далее напишем класс для запуска теста

Здесь мы увидим, что объект animal имеет тип Animal, соответствующий родительскому классу. Создаем экземпляры классов, а затем вызов метода ничем не отличается для разных “животных”. Это и есть реализация полиморфизма.

Результат выполнения программы

В родительском классе не обязательно определять реализацию того или иного метода. В Java существует возможность вообще не определять реализацию методов класса. Такие методы становятся абстрактными, а сам класс – абстрактным, от абстрактного класса нельзя создать объект, абстрактные методы предназначены только для наследования и не имеют конкретной реализации.  От абстрактного класса можно создать дочерний класс, в нем необходимо прописать реализацию абстрактных методов, в противном случае дочерний класс тоже будет абстрактным. Чтобы продемонстрировать все это, перепишем класс Animal, сделаем его абстрактным:

В консоли увидим:

А как же быть, если мы хотим одни методы реализовать, а другие оставить абстрактными? Абстрактный класс предоставляет нам такую возможность. Но вот если мы хотим все методы сделать абстрактными, для этого мы можем использовать интерфейс – Java Interface. Интерфейс не содержит реализованных методов, в интерфейсе есть только поля и абстрактные методы. Перепишем класс Animal,  сделаем его интерфейсом.

В таком случае классы Dogs и Cats претворяют (implements) интерфейс Animal.

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


Связанный список

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

Данные последовательности могут храниться в массивах, но связные списки обеспечивают ряд преимуществ. Связанный список-это уместно, если число элементов данных, которые будут представлены в структуре данных непредсказуем. Связные списки являются динамическими, поэтому длина списка может увеличиваться или уменьшаться по мере необходимости. Размер “обычного” массива в java не может быть изменен, потому что размер массива фиксируется в тот момент, когда программа создает массив.

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

В этом примере рассмотрим действия со связанными списками.

Результат выполнения программы:

 


Бинарные деревья. Обход в прямом порядке

Связные списки, стеки и очереди являются линейными структурами данных (это последовательности). Деревья представляют собой нелинейную двумерную структуру данных со специальными свойствами. Узлы дерева содержат две или более ссылок. В этом примере рассматриваются бинарные деревья, т.е. деревья, узлы которого содержат по две ссылки (одна или обе из которых могут быть NULL).
Корневой узел является первым узлом дерева. Каждая ссылка в корневом узле указывает на дочерний узел или узел-потомок. Левый узел-потомок является первым узлом в левом поддереве, а правый узел-потомок является первым узлом в правом поддереве. Узлы-потомки, принадлежащие одному какому-либо узлу, называются родственными узлами. Узел без узлов-потомков называется листом дерева или концевым узлом. Компьютерщики обычно рисуют деревья сверху-вниз, начиная с корневого узла, то есть противоположно тому, как растут деревья в природе.
В этом примере рассмотрим обход бинарного дерева в прямом порядке. Каждый узел посещается до того, как посещены его потомки.

Для корня дерева выполняются следующие шаги:

  • Посетить узел
  • Обойти левое поддерево
  • Обойти правое поддерево

Это может быть реализовано двумя способами

  • рекурсивно
  • итерационно (пошагово)

Шаги для итерационного решения:
Создать пустой стек и поместить в него корневой узел.
Выполните следующие действия, до тех по пока стек не пуст:
Возьмите узел из стека, и распечатайте его
Поместите правого потомка узла в стек
Поместите левого потомка узла в стек

Мы помещаем первым правого потомка, но он будет обработан после левого поддерева, так как стек организован по принципу Last In First Out (LIFO).

Результат выполнения программы: