Имеется два способа создать класс, экземплярами которого будут потоки выполнения: унаследовать класс от java.lang.Thread либо реализовать интерфейс java.lang.Runnable. Этот интерфейс имеет декларацию единственного метода public void run(), который обеспечивает последовательность действий при работе потока. При этом класс Thread уже реализует интерфейс Runnable, но с пустой реализацией метода run().Так что при создании экземпляра Thread создаётся поток, который ничего не делает. Поэтому в потомке надо переопределить метод run(). В нём следует написать реализацию алгоритмов, которые должны выполняться в данном потоке. Отметим, что после выполнения метода run() поток прекращает существование – “умирает”.
Рассмотрим первый вариант, когда мы наследуем класс от класса Thread, переопределив метод run().
Объект-поток создаётся с помощью конструктора. Имеется несколько перегруженных вариантов конструкторов, самый простой из них – с пустым списком параметров. Например, в классе Thread их заголовки выглядят так:
public Thread() – конструктор по умолчанию. Подпроцесс получает имя “system”.
public Thread(String name) - поток получает имя, содержащееся в строке name.
Также имеется возможность создавать потоки в группах. Но в связи с тем, что данная технология устарела и не нашла широкого распространения, о группах потоков выполнения в данном учебном пособии рассказываться не будет.
В классе-потомке можно вызывать конструктор по умолчанию (без параметров), либо задать свои конструкторы, используя вызовы прародительских с помощью вызова super(список параметров). Из-за отсутствие наследования конструкторов в Java приходится в наследнике заново задавать конструкторы с той же сигнатурой, что и в классе Thread. Это является простой, но утомительной работой. Именно поэтому обычно предпочитают способ задания класса с реализацией интерфейса Runnable, о чём будет рассказано несколькими строками позже.
Создание и запуск потока осуществляется следующим образом:
public class T1 extends Thread{
public void run(){
...
}
...
}
Thread thread1= new T1();
thread1.start();
Второй вариант – использование класса, в котором реализован интерфейс java.lang.Runnable. Этот интерфейс, как уже говорилось, имеет единственный метод public void run(). Реализовав его в классе, можно создать поток с помощью перегруженного варианта конструктора Thread:
public class R1 implements Runnable{
public void run(){
...
}
...
}
Thread thread1= Thread(new R1());
thread1.start();
Обычно таким способом пользуются гораздо чаще, так как в разрабатываемом классе не приходится заниматься дублированием конструкторов класса Thread. Кроме того, этот способ можно применять в случае, когда уже имеется класс, принадлежащий иерархии, в которой базовым классом не является Thread или его наследник, и мы хотим использовать этот класс для работы внутри потока. В результате от этого класса мы получаем метод run(), в котором реализован нужный алгоритм, и этот метод работает внутри потока типа Thread, обеспечивающего необходимое поведение в многопоточной среде. Однако в данном случае затрудняется доступ к методам из класса Thread – требуется приведение типа.
Например, чтобы вывести информацию о приоритете потока, в первом способе создания потока в методе run() надо написать оператор
System.out.println("Приоритет потока="+this.getPriority());
А во втором способе приходиться это делать в несколько этапов. Во-первых, при задании класса нам следует добавить в объекты типа R1 поле thread:
public class R1 implements Runnable{
public Thread thread;
public void run() {
System.out.println("Приоритет потока="+thread.getPriority());
}
}
С помощью этого поля мы будем добираться до объекта-потока. Но теперь после создания потока необходимо не забыть установить для этого поля ссылку на созданный объект-поток. Так что создание и запуск потока будет выглядеть так:
R1 r1=new R1();
Thread thread1=new Thread(r1, "thread1");
r1.thread=thread1;
thread1.start();//либо, что то же, r1.thread.start()
Через поле thread мы можем получать доступ к потоку и всем его полям и методам в алгоритме, написанном в методе run(). Указанные выше дополнительные действия – это всего три лишних строчки программы (первая - R1 r1=new R1(); вторая - r1.thread=thread1; третья - объявление в классе R1 - public Thread thread;).
Как уже говорилось ранее, напрямую давать доступ к полю данных – дурной тон программирования. Исправить этот недостаток нашей программы просто: в дереве элементов программы окна Projects в разделе Fields (“поля”) щёлкнем правой кнопкой мыши по имени thread и выберем в появившемся всплывающем меню Refactor/Encapsulate Fields… (“Провести рефакторинг”/ “Инкапсулировать поля…”). В появившемся диалоге нажмём на кнопку “Next>” и проведём рефакторинг, подтвердив выбор в нижнем окне.
В классе Thread имеется несколько перегруженных вариантов конструктора с параметром типа Runnable:
public Thread(Runnable target) – с именем “system” по умолчанию.
public Thread(Runnable target, String name) – с заданием имени.
Также имеются варианты с заданием группы потоков.