Выбрать главу

В качестве параметра threadOb этому конструктору передается экземпляр класса, реализующего интерфейс Runnable. Благодаря этому определяется место для исполнения потока.

Созданный поток не начнет исполнение до тех пор, пока не будет вызван метод start (), объявленный в классе Thread. По существу, единственным назначением метода start () является вызов метода run (). А объявляется метод start () следующим образом: void start()

Ниже приведен пример программы, в которой создается и запускается на исполнение новый поток. // Создание потока путем реализации интерфейса Runnable, class MyThread implements Runnable { String thrdName; // Объекты типа MyThread выполняются в отдельных потоках, так как // класс MyThread реализует интерфейс Runnable. MyThread(String name) { thrdName = name; } // Точка входа в поток, public void run() { // Здесь начинают исполняться потоки. System.out.println(thrdName + " starting."); try { for(int count=0; count < 10; count++) { Thread.sleep(400); System.out.println("In " + thrdName + ", count is " + count); } } catch(InterruptedException exc) { System.out.println(thrdName + " interrupted."); } System.out.println(thrdName + " terminating."); } } class UseThreads { public static void main(String args[]) { System.out.println("Main thread starting."); // сначала построить объект типа MyThread MyThread mt = new MyThread("Child #1"); // Создание исполняемого объекта. // далее сформировать поток из этого объекта Thread newThrd = new Thread(mt); // Формирование потока из этого объекта. // и, наконец, начать исполнение потока newThrd.start О; // Начало исполнения потока. for(int i=0; i<50; i++) { System.out.print(".") ; try { Thread.sleep(100) ; } catch(InterruptedException exc) { System.out.println("Main thread interrupted."); } } System.out.println("Main thread ending."); } }

Рассмотрим исходный код приведенной выше программы более подробно. Как видите, класс MyThread реализует интерфейс Runnable. Это означает, что объект типа MyThread подходит для использования в качестве потока, а следовательно, его можно передать конструктору класса Thread.

В теле метода run () присутствует цикл, в котором производится отсчет от 0 до 9. Обратите внимание на вызов метода sleep (). Этот метод приостанавливает поток, из которого он был вызван на указанное число миллисекунд. Ниже приведена общая форма объявления данного метода. static void sleep(long миллисекунд) throws InterruptedException

Единственный параметр метода sleep () задает время задержки, определяемое числом миллисекунд. Как следует из объявления этого метода, в нем может быть сгенерировано исключение InterruptedException. Следовательно, его нужно вызывать в блоке try. Имеется и другой вариант метода sleep (), позволяющий точнее указывать время задержки в миллисекундах и дополнительно в наносекундах. Когда метод sleep () вызывается в методе run (), исполнение потока приостанавливается на 400 миллисекунд на каждом шаге цикла. Благодаря этому поток исполняется достаточно медленно, чтобы можно проследить за ним.

В методе main () создается новый объект типа Thread. Для этой цели служит приведенная ниже последовательность операторов. // сначала построить объект типа MyThread MyThread mt = new MyThread("Child #1"); // далее сформировать поток из этого объекта Thread newThrd = new Thread(mt); // и, наконец, начать исполнение потока newThrd.start();

Как видите, сначала создается объект типа MyThread, а затем он используется для построения объекта типа Thread. Его можно передать конструктору класса Thread в качестве параметра, поскольку класс MyThread реализует интерфейс Runnable. И наконец, начинается исполнение нового потока, для чего вызывается метод start (), что приводит к вызову метода run () из порожденного потока. После вызова метода start () управление возвращается к методу main (), где начинается выполнение цикла for. Этот цикл повторяется 50 раз, приостанавливая на 100 миллисекунд исполнение потока на каждом своем шаге. Оба потока продолжают исполняться, разделяя ресурсы

ЦП в однопроцессорной системе до тех пор, пока циклы в них не завершатся. Ниже приведен результат выполнения данной программы. Вследствие отличий в вычислительных средах у вас может получиться несколько иной результат. Main thread starting. .Child #1 starting. ....In Child #1, count is 0 .....In Child #1, count is 1 .....In Child #1, count is 2 .....In Child #1, count is 3 .....In Child #1, count is 4 .....In Child #1, count is 5 .....In Child #b count is 6 .....In Child #1, count is 7 .....In Child #1, count is 8 .....In Child #1 count is 9 Child #1 terminating. Main thread ending

В рассматриваемом здесь первом примере организации многопоточной обработки любопытно также отметить следующее обстоятельство: для демонстрации того факта, что основной и порожденный потоки исполняются одновременно, необходимо задержать завершение метода main () до тех пор, пока не окончится порожденный поток mt. В данном примере это достигается благодаря отличиям во временных характеристиках обоих потоков. Вызовы метода sleep () из цикла for в методе main () приводят в итоге к задержке на 5 секунд (50 шагов цикла х 100 миллисекунд), тогда как общая задержка с помощью того же самого метода в аналогичном цикле в методе run () составляет лишь 4 секунды (10 шагов цикла х 400 миллисекунд). Поэтому метод run () завершится приблизительно на 1 секунду раньше, чем метод main (). В итоге основной и порожденный потоки будут выполняться параллельно до тех пор, пока не завершится порожденный поток mt. А приблизительно через одну секунду завершится и основной поток в методе main ().

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

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

Рассмотренная выше многопоточная программа вполне работоспособна, тем не менее ей не помешает небольшая доработка, повышающая ее эффективность. Во- первых, можно сделать так, чтобы поток начинал исполняться сразу после создания. Эта цель достигается созданием экземпляра объекта типа Thread в конструкторе класса MyThread. И во-вторых, нет никакой нужды хранить в объекте типа MyThread имя потока, но присвоить имя потоку при его создании. Эту задачу позволяет решить следующий вариант конструктора Thread: Thread(Runnable threadOb, String имя)

где имя обозначает конкретное наименование потока.

Получить имя потока можно, используя метод getName (), определенный в классе Thread. Ниже приведено объявление этого метода. final String getName()

В приведенной ниже программе имя присваивается потоку после его создания с помощью метода setName (). И хотя в этом нет особой необходимости, такое решение выбрано лишь для того, чтобы продемонстрировать возможности класса Thread. Объявление метода setName () имеет следующий вид: final void setName(String имя_потока)

где имя_потока обозначает имя, которое присваивается потоку.

Ниже приведена видоизмененная версия предыдущей программы. // Видоизменение класса MyThread. class MyThread implements Runnable { Thread thrd; // В этой переменной хранится ссылка на поток. // построить новый поток MyThread(String name) { thrd = new Thread(this, name); // Поток именуется при его создании, thrd.start() ; // Начало исполнения потока. } // начать исполнение нового потока public void run() { System.out.println(thrd.getName() + " starting."); try { for (int count=0; countclO; count++) { Thread.sleep(400); System.out.println("In " + thrd.getName() + ", count is " + count)'; } } catch(InterruptedException exc) { System.out.println(thrd.getName() + " interrupted."); } System.out.println(thrd.getName() + " terminating."); } } class UseThreadsImproved { public static void main(String args[]) { System.out.println("Main thread starting."); // Теперь поток начинается при его создании. MyThread mt = new MyThread("Child #1"); for (int i=0; i < 50; i++) { System.out.print(".") ; try { Thread.sleep(100) ; } catch(InterruptedException exc) { System.out.println("Main thread interrupted."); } } System.out.println("Main thread ending."); } }