Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Класс Thread и интерфейс Runnable




Для того чтобы на простом примере увидеть преимущество от использования отдельных потоков, попробуем решить следующую задачу. Допустим необходимо выводить на консоль символ с заданной периодичностью. Например, напечатать 10 раз букву "A" с периодичность примерно 250 миллисекунд. Можно предложить следующее простое решение «в лоб»:

 

public class Example {

public static void main(String[] args) { for(int i =0; i<10; i++) {

for (int j =0; j < 1_000_000_000L; j++) {}

System.out.println("A");

}}}

 

В примере внешний цикл задает счетчик для вывода символа "A" 10 раз, а внутренний цикл эмулирует таймер, загружая процессор «холостой» работой по перебору значений счетчика j от 0 до 1000000000. Задача решена. Однако процессор будет загружен все время выполнения программы, несмотря на то, что вывод символа на экран – «полезная» работа выполняется только каждые 250 мс.

Усложним задачу: необходимо выводить на экран два символа с разной периодичностью. Например, символ "А" каждые 130 мс. и символ "В" каждые 80 мс. Можно конечно организовать два независимых цикла, подобных тому, что мы реализовали в классе Example. Первый будет выводить символ "А", второй - символ "В" (рис.1).

 

 

Рис. 1 – Диаграмма вывода символов с заданной

периодичностью в отдельных независимых циклах

 

Но как это выполнить в одном цикле и одновременно? Такая задача уже не является столь простой и требует расчета временных промежутков между выводами символов (рис.2).

 

 

Рис. 2 – Диаграмма вывода символов с заданной

периодичностью в одном цикле

 

Обе задачи просто и эффективно можно решить, используя потоки. Существуют два способа создания и запуска потока: расширение класса Thread или реализация интерфейса Runnable из пакета java.lang. В классе Thread имеется статический метод sleep(), который указывает потоку уснуть на заданное количество миллисекунд. Перепишем класс Example:

 

public class ThreadExample {

public static void main(String[] args)

throws InterruptedException{

for(int i =0; i<10; i++) {

Thread.sleep(250);

System.out.println("A");

}}}

 

Программа выполнит те же действия, что и класс Example, но при этом процессор не будет загружен все время выполнения программы. В примере используется статический метод sleep(), который бросает проверяемое (cheсked) исключение InterruptedException. Аргументом метода является значение типа long – время в миллисекунд, на которое наш поток (метод main()) должен потерять активность. При вызове метода sleep() JVM обращается к операционной системе и уже ОС останавливает выполнение потока на указанное время, при этом CPU не работает. Данный метод относится к блокирующим методам.

Приведем один из вариантов решения задачи с выводом на экран букв "А" и "В" с помощью потоков.

Создадим класс ThreadLetterB с реализацией интерфейса Runnable. Интерфейс Runnable содержит один единственный абстрактный метод run(), который следует реализовать. В методе run() будем 10 раз печатать букву "В" с периодичностью 80 мс.

 

public class ThreadLetterB implements Runnable {

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(" B");

try {

Thread.sleep(80);

} catch (InterruptedException e) {

System.err.println(e);

}

}

}

}

 

В классе Example запускаем одновременно два потока, один – поток метода main(), печатающий букву "А", а второй – поток метод run()класса ThreadLetterB, печатающий букву "В".

 

public class Example {

public static void main(String[] args) throws InterruptedException{

//Шаблон Command [3]

Thread thread = new Thread (new ThreadLetterB ());

thread.start();

 

//только после start() пишем цикл – наоборот нельзя!!!

for(int i =0; i<10; i++) {

Thread.sleep(130);

System.out.println("A");

}

}

}

В программе был создан второй поток thread, для этого в конструктор Thread() был добавлен экземпляр класса ThreadLetterB, реализующий интерфейс Runnable. После вызова для экземпляра thread метода start() некая сущность операционной системы (не JVM) запустит метод run(), печатающий букву "В", при этом JVM продолжит выполнять дальше инструкции метода main(): запустит цикл с выводом на экран букв "А". Таким образом, одновременно будут выполняться два потока. Вызов метода start()– это единственный способ в Java породить независимую активность (при прямом вызове метода run() поток не запустится, выполнится только тело самого метода). Эта активность называется потоком выполнения [4] или нитью. Если бы в примере вместо метода thread.start() был вызван метод thread.run() новый поток не был бы порожден. В этом случае цикл вывода букв "А" стартовал бы только после того, как выполнился метод run(), т.е. вывод бы осуществлялся ПОСЛЕДОВАТЕЛЬНО: сначала все буквы "В", а потом все буквы "А".

С каждым потоком связан стек вызовов. В отличие от стека вызова рекурсивного метода, у которого всегда один корень – метод main() и одна точка активности, которая всегда возвращалась в корень, стек вызова потоков может иметь несколько корней (в нашем примере методы main() и run()) и одновременно несколько точек активности. При этом может возникнуть ситуация, когда метод main() уже закончил работу, а порожденные потоки еще работают (так бы было, если бы мы в программе вызвали метод thread.start() после цикла, печатающего буквы "А").

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

Вариант 1. Создано два класса, один расширяется от Thread другой реализует интерфейс Runnable.

public class Thread1 extends Thread {

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println("A");

try {

Thread.sleep(130);

} catch (InterruptedException e) {

System.err.print(e);

}

}

}

}

public class Thread2 implements Runnable {

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(" B");

try {

Thread.sleep(80);

} catch (InterruptedException e) {

System.err.println(e);

}

}

}

}

 

public class Example {

public static void main(String[] args) {

Thread1 thread1 = new Thread1();

Thread thread2 = new Thread(new Thread2());

thread1.start();

thread2.start();

}

}

Вариант 2. Создан класс, реализующий интерфейс Runnable с конструктором, который получает в качестве аргументов текст для печати и периодичность его вывода в миллисекундах. В методе main() создаются два экземпляра данного класса и запускаются их методы run()в двух параллельно работающих потоках выполнения.

 

public class ExampleRunnable implements Runnable{

private String msg;

private long sleepMillis;

 

public ExampleRunnable(String msg, long sleepMillis) {

this.msg = msg;

this.sleepMillis = sleepMillis;

}

public void run(){

for (int i=0; i<10; i++) {

try{

Thread.sleep(sleepMillis);

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

System.out.println(msg);

}

}}

 

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread1 = new Thread (new ExampleRunnable(" B", 80));

Thread thread2 = new Thread (new ExampleRunnable("A", 130));

thread1.start();

thread2.start();

 

}}

 

 






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных