Этой статьей я начинаю цикл статей по многопоточности и параллелизму в Java. Сегодня познакомимся с Thread и интерфейсом Runnable. Для того, чтобы понимать многопоточность в Java, нужно знать некоторые понятия.
Процесс в Java
Процесс представляет собой автономную среду выполнения и может рассматриваться в качестве программы или приложения. Однако сама программа содержит несколько процессов внутри себя. Например, среда выполнения Java работает как единый процесс, который содержит различные классы и программы.
Thread в Java
Thread можно назвать легковесным процесс. Он требует меньше ресурсов для создания и существует в процессе, деля с ним ресурсы.
Многопоточность в Java
Каждая Java программа работает как минимум с одним потоком — главным потоком. Несмотря на то, что есть очень много других потоков, работающих в фоновом режиме: управление памятью, управление системой, обработка сигналов и т.д. Но с точки зрения нашей программы — главным будет первый поток.
Многопоточность — это два и больше потоков, выполняющихся одновременно в одной программе. Компьютер с одноядерным процессором может выполнять только один поток, деля процессорное время между различными процессами и потоками.
Преимущества потоков
- Потоки намного легче процессов, они требуют меньше времени и ресурсов.
- Переключение контекста между потоками намного быстрее, чем между процессами.
- Намного проще добиться взаимодействия между потоками, чем между процессами.
Java предоставляет два способа программно создать поток.
- Реализация интерфейса
java.lang.Runnable
. - Расширение класса
java.lang.Thread
.
Пример создания Thread. Реализуем интерфейс Runnable
Для того, чтобы класс был runnable, мы должны реализовать интерфейс java.lang.Runnable
и обеспечить реализацию метода public void run()
. Чтобы использовать этот класс, как поток, мы должны создать объект Thread, передавая объект runnable класса, а затем вызвать метод start()
, чтобы выполнился метод r
un()
в отдельном потоке.
Вот пример Java класса, реализующего Runnable интерфейс.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package ua.com.prologistic; public class HeavyWorkRunnable implements Runnable { @Override public void run() { System.out.println("Начинаем обработку в отдельном потоке - " + Thread.currentThread().getName()); try { Thread.sleep(1000); // для примера будем выполнять обработку базы данных doDBProcessing(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Заканчиваем обработку в отдельном потоке - " + Thread.currentThread().getName()); } // метод псевдообработки базы данных private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } } |
Пример создания потока. Наследуем класс Thread
Мы можем наследовать класс java.lang.Thread
для создания собственного класса Thread и переопределить метод run()
. Тогда мы можем создать экземпляр этого класса и вызвать метод start()
для того, чтобы выполнить метод run()
.
Вот простой пример того, как наследоваться от класса Thread:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package ua.com.prologistic; public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println("Стартуем наш поток " + Thread.currentThread().getName()); try { Thread.sleep(1000); // для примера будем выполнять обработку базы данных doDBProcessing(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Заканчиваем наш поток " + Thread.currentThread().getName()); } // метод псевдообработки базы данных private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } } |
Вот тестовая программа, показывающая наш поток в работе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package ua.com.prologistic; public class ThreadRunExample { public static void main(String[] args){ Thread t1 = new Thread(new HeavyWorkRunnable(), "t1"); Thread t2 = new Thread(new HeavyWorkRunnable(), "t2"); System.out.println("Стартуем runnable потоки"); t1.start(); t2.start(); System.out.println("Runnable потоки в работе"); Thread t3 = new MyThread("t3"); Thread t4 = new MyThread("t4"); System.out.println("Стартуем наши кастомные потоки"); t3.start(); t4.start(); System.out.println("Кастомные потоки в работе"); } } |
Runnable vs Thread
Если ваш класс предоставляет больше возможностей, чем просто запускаться в виде Thread, то вам лучше реализовать интерфейс Runnable. Если же вам просто нужно запустить в отдельном потоке, то вы можете наследоваться от класса Thread.
Реализация Runnable является более предпочтительной, поскольку Java поддерживает реализацию нескольких интерфейсов. Если вы наследуете класс Thread, то вы уже не можете наследовать другие классы.
Интересно знать
Начиная с Java 8, Runnable представляет собой функциональный интерфейс и мы можем использовать лямбда-выражения для его реализации, вместо анонимного класса. Следите за обновлениями сайта и вы увидите полное руководство по лямбда выражениям в Java!