Многопоточность в Java
Как известно современная компьютер позволяет работать нескольким программам одновременно. На самом деле это не всегда правда. Дело в том, что любая операция происходит в процессоре. Код программ выполняет центральный процессор, а точнее его АЛУ (арифметико-логическое устройство). АЛУ + кэш-память составляет ядро процессора. В один момент времени на одном ядре может выполняться только одна инструкция. Пользователю компьютера кажется что несколько программ работает одновременно только потому, что процессор быстро выполняет подаваемые ему инструкции, а операционная система быстро переключается между программами и передаёт эти инструкции на процессор.
Самая простая программа состоит из одного потока, но современным программам этого недостаточно. Если необходимо, чтобы программа псевдо-одновременно следила за несколькими воздействиями, выполняла несколько псевдо-паралельных операций и т.д., то нам не обойтись без создания многопоточности в нашей программе. Каждый поток в программе фактически является небольшой программкой, которая работает независимо. Для того чтобы объединить и скоординировать все работающие в программе потоки необходимо разрабатывать координирующий поток или систему общения между потоками.
Приступим к практике. Создайте новый проект, назовите его например Zmey_Console_Threads_Example. Автоматически созданный основной класс Main мы будем использовать как координирующий поток. Приведём его вид к следующему:
package zmey_console_threads_example;
/** * * @author Zmey * @author http://shendenkov.at.ua */public class Main {
/** * @param args the command line arguments */ public static void main(String[] args) { // Флаги для остановки потоков boolean stoped_a = false; boolean stoped_b = false; boolean stoped_c = false; // Создаём потоки Thread_A a = new Thread_A(); Thread_B b = new Thread_B(); Thread_C c = new Thread_C(); // Начальное время в миллисекундах long starting_time = System.currentTimeMillis(); // Старт потоков a.start(); b.start(); c.start(); // Пока хоть один из потоков работает while (a.isAlive() || b.isAlive() || c.isAlive()) { // Если с начального времени прошло 60 секунд if (starting_time + 60000 < System.currentTimeMillis() && !stoped_a) { a.exit(); stoped_a = true; System.out.println("stoping Thread A"); } // Если с начального времени прошло 120 секунд if (starting_time + 120000 < System.currentTimeMillis() && !stoped_b) { b.exit(); stoped_b = true; System.out.println("stoping Thread B"); } // Если с начального времени прошло 180 секунд if (starting_time + 180000 < System.currentTimeMillis() && !stoped_c) { c.exit(); stoped_c = true; System.out.println("stoping Thread C"); } } }
}
Далее создадим ещё три класса в том же пакете. Каждый класса будем наследовать от класса потока (Thread) используя ключевое слово extends.
Поток A:
package zmey_console_threads_example;
/** * * @author Zmey * @author http://shendenkov.at.ua */public class Thread_A extends Thread {
public boolean working = true;
@Override public void run() { // Начальное знаение счётчика int i = 1000; // Пока поток должен работать while (working) { try { System.out.println("Thread A sleep " + i + " miliseconds"); // Пауза на указанное количество миллисекунд sleep(i); // Добавляем 10. Это выражение равнозначно i = i + 10 i += 10; } catch (InterruptedException ex) { } } }
public void exit() { working = false; }}
Поток B:
package zmey_console_threads_example;
/** * * @author Zmey * @author http://shendenkov.at.ua */public class Thread_B extends Thread {
public boolean working = true;
@Override public void run() { int i = 2000; while (working) { try { System.out.println("Thread B sleep " + i + " miliseconds"); sleep(i); i += 20; } catch (InterruptedException ex) { } } }
public void exit() { working = false; }}
Поток C:
package zmey_console_threads_example;
/** * * @author Zmey * @author http://shendenkov.at.ua */public class Thread_C extends Thread {
public boolean working = true;
@Override public void run() { int i = 3000; while (working) { try { System.out.println("Thread C sleep " + i + " miliseconds"); sleep(i); i += 30; } catch (InterruptedException ex) { } } }
public void exit() { working = false; }}
Как мы можем видеть, классы-потоки, которые мы создали, очень похожи друг на друга. Разница только в счётчике, который мы используем для паузы потока. Это создано для примера, на практике один поток может обрабатывать сетевые коммуникации, другой поток работать с базой данных и т.д.
Давайте разберёмся, что выполняет каждый поток.
Классы потоков A, B и C наследуются от класса Thread, который заставляет нас создать метод run в каждом из них. Для того чтобы поток начал свою работу его мало создать (для этого используется конструктор), его ещё и необходимо запустить (для этого используется метод start суперкласса - Thread). Само выполнение потока происходит в методе run.
После старта поток инициализирует счётчик и входит в условный цикл while. В цикле поток выводит на консоль сообщение. Это и есть "полезная работа" в нашем потоке. Затем поток уходит в паузу на указанное количество миллисекунд и изменяем счётчик. Выход из цикла, а значит и окончание работы потока, произойдёт только тогда когда переменная working измениться на false.
В координирующем потоке Main мы создаём три потока, замеряем время перед стартом, стартуем потоки и входим в цикл слежения за их состоянием. Пока работает хоть один из наших потоков и будет работать координирующий поток. Мы с Вами отвели на работу потока A 1 минуту, на работу потока B - 2 минуты и на работу потока C - 3 минуты. Для того чтобы остановить поток я рекомендую использовать логическую переменную working, потому что метод interrupt, который предполагался для того, чтобы прерывать и останавливать поток, к сожалению, не работает. Поэтому единственный способ корректно остановит поток это дать ему завершиться самостоятельно. Если время, которое должен выполняться поток неизвестно, то его можно остановить выйдя из условного цикла.
Запустите программу в NetBeans или консоли и понаблюдайте за её действиями в течении 3 минут. Вы должны увидеть что действия каждого из потоков являются независимыми. Можете поэкспериментировать с разными временными промежутками. Вот мы и научились создавать, запускать и останавливать потоки в Java.