Далее на странице
Создание потоков позволяет выполнять несколько задач одновременно, что ускоряет процесс выполнения программы.
.Существует два основных способа создания потоков в Java.
Реализация интерфейса Runnable.
и1 способ. Наследование от класса Thread
Первый способ создания потоков в Java заключается в наследовании от класса Thread.
Что для этого нужно?
Нужно создать новый класс, который наследует класс Thread, и переопределить метод run().
Затем создать экземпляр нового класса и вызвать метод start().
Java-код. Пример 1.1
public class CreateThread1 {
public static void main(String[] args) {
MyThread myThread = new MyThread(); // Экземпляр класса MyThread. Создание нового потока
myThread.start(); // start() - запускает поток и вызывает метод run()
// myThread.start(); // ! см. Примечание №1
MyThread myThread2 = new MyThread();
myThread2.start();
}
}
class MyThread extends Thread { // MyThread наследует класс Thread
@Override
public void run() { // Метод run()
System.out.println("Привет " + Thread.currentThread().getName()); // Код, который выполняет поток
}
} // -- level_11/
Результат в консоли
Привет Thread-0
Привет Thread-1
Примечание №1. , даже если поток завершён. Иначе будет выброшено исключение IllegalThreadStateException.
Метод run() - не запускает поток, а просто , которая может работать параллельно.
Метод start() - запускает поток и .
Thread.currentThread().getName() возвращает имя текущего выполняющегося потока.
Это происходит благодаря тому, что метод Thread.currentThread() возвращает объект класса Thread, а затем на этом объекте вызывается .
Метод sleep - порядок выполнения потоков
Потоки не выполняются последовательно. В примере выше, мы могли бы увидеть обратный порядок выполнения потоков.
Привет Thread-1
Привет Thread-0
Если добавить еще несколько потоков, то можно убедиться в этом.
С помощью метода sleep - можно
.Java-код. Пример 1.2
public class CreateThread1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread(); // поток №1
myThread.start();
MyThread myThread2 = new MyThread(); // поток №2
myThread2.start();
new MyThread().start(); // Можно и так - 2 новых потока
new MyThread().start();
// ----------------- Метод sleep() ----------------------------------------------------
Thread.sleep(500); // Поток main СПИТ 500 мс.
System.out.println("Привет, я метод sleep() потока " + Thread.currentThread().getName());
for (int i = 0; i < 3; i++) {
Thread.sleep(1000); // Текущий поток СПИС 1 с.
new MyThread().start();
}
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Привет " + Thread.currentThread().getName());
}
} // -- level_11/
Результат в консоли
Привет Thread-0
Привет Thread-2
Привет Thread-3
Привет Thread-1
Привет, я метод sleep() потока main
Привет Thread-4
Привет Thread-5
Привет Thread-6
Метод Thread.sleep()
.Он принимает в качестве параметра число миллисекунд — то время, на которое необходимо «усыпить» поток.
Метод sleep() — статический: он усыпляет текущий поток, то есть тот, который работает в данный момент.
Поток в состоянии сна можно прервать. В таком случае в программе возникнет исключение InterruptedException.
2 способ. Реализация интерфейса Runnable
Второй способ создания потоков в Java заключается в реализации интерфейса Runnable.
Что это значит? Что нужно сделать для создания потока?
Нужно создать класс, который реализует интерфейс Runnable, и переопределить метод run().
Затем создать экземпляр Thread и передать в конструктор свой Runnable ( , реализующий этот интерфейс).
Java-код. Интерфейс Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Реализация интерфейса Runnable - это альтернативный способ создания потока. Вместо наследования от класса Thread создаётся класс, который реализует интерфейс Runnable, и переопределяется метод run().
Java-код. Пример 2.1
public class CreateThread2 {
public static void main(String[] args) {
ClassRunnable classRunnable = new ClassRunnable();
Thread thread = new Thread(classRunnable); // 1. Создание нового потока с передачей в конструктор
// класса Thread объекта classRunnable
Thread thread1 = new Thread(new ClassRunnable()); // 2. Аналогичная запись: одна строка объединяет 2 предыдущих
thread.start(); // Запуск потоков
thread1.start();
}
}
class ClassRunnable implements Runnable { // ClassRunnable имплементирует интерфейс Runnable
@Override
public void run() {
System.out.println("Привет " + Thread.currentThread().getName());
}
} // -- level_11/
Результат в консоли
Привет Thread-0
Привет Thread-1
Последовательность выполнения потоков
Каждый поток выполнит действие 100 раз.
, чтобы увидеть как работают потоки.Java-код. Пример 2.2
public class CreateThread2 {
public static void main(String[] args) {
ClassRunnable classRunnable = new ClassRunnable();
Thread thread = new Thread(classRunnable);
Thread thread1 = new Thread(new ClassRunnable());
thread.start();
thread1.start();
}
}
class ClassRunnable implements Runnable {
@Override
public void run() { // Добавляем цикл
for (int i = 0; i < 100; i++) {
System.out.println("Привет №" + i + ". Я поток " + Thread.currentThread().getName());
}
}
} // -- level_11/
Результат в консоли
.
.
Привет №19. Я поток Thread-0
Привет №20. Я поток Thread-0
Привет №21. Я поток Thread-0
Привет №0. Я поток Thread-1
Привет №1. Я поток Thread-1
Привет №2. Я поток Thread-1
.
.
Привет №24. Я поток Thread-0
Привет №10. Я поток Thread-1
Привет №25. Я поток Thread-0
Привет №11. Я поток Thread-1
.
.
Из примера видно, что в работе потоков нет последовательности:
первые 2 итерации цикла "Привет №0" и "Привет №1" - это НЕ первый поток Thread-0, а второй Thread-1, да и .
Последовательность выполнения потоков в Java контролировать нельзя. В каком порядке запускать новые потоки, решает планировщик потоков — часть JVM, которая определяет, какой поток должен выполниться в каждый конкретный момент времени и какой поток нужно приостановить.
Потоки в Java работают параллельно, и без дополнительных усилий нельзя предполагать, что какая-то операция в одном потоке отработает раньше или позже какой-либо операции в другом.
Потоки в цикле. Методы sleep() и join()
Запускаем потоки в цикле. Используем методы и join().
Java-код. Пример 2.3
public class CreateThread2 {
public static void main(String[] args) throws InterruptedException {
ClassRunnable classRunnable = new ClassRunnable();
Thread thread = new Thread(classRunnable); // Создание нового потока с передачей в конструктор
// класса Thread объекта classRunnable
Thread thread1 = new Thread(new ClassRunnable()); // Аналогичная запись: одна строка объединяет 2 предыдущих
thread.start(); // Запуск потоков Thread-0 и Thread-1
thread.join(); // Метод join() используется, чтобы сначала отработал поток Thread-0. Thread-1 при этом ЖДЕТ
thread1.start();
Thread.sleep(500); // Метод main спит 0,5 секунды, чтобы сначала отработали 2 потока
System.out.println("--------- Метод main проснулся. Первые 2 потока отработали -----------------");
for (int i = 0; i < 5; i++) { // Еще 5 потоков. Каждый выводит приветствие 2 раза (цикл for в run())
Thread.sleep(2000); // Каждый поток спит 2 секунды
new Thread(new ClassRunnable()).start();
}
}
}
class ClassRunnable implements Runnable { // ClassRunnable имплементирует интерфейс Runnable
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("Привет №" + i + ". Я поток " + Thread.currentThread().getName());
}
}
} // -- level_11/
Результат в консоли
Привет №0. Я поток Thread-0
Привет №1. Я поток Thread-0
Привет №0. Я поток Thread-1
Привет №1. Я поток Thread-1
--------- Метод main проснулся. Первые 2 потока отработали -----------------
Привет №0. Я поток Thread-2
Привет №1. Я поток Thread-2
Привет №0. Я поток Thread-3
Привет №1. Я поток Thread-3
Привет №0. Я поток Thread-4
Привет №1. Я поток Thread-4
Привет №0. Я поток Thread-5
Привет №1. Я поток Thread-5
Привет №0. Я поток Thread-6
Привет №1. Я поток Thread-6
- Метод join()
То есть в нашем случае:
1. Метод join() thread.join() - приостанавливает выполнение потока Thread-1 пока не выполниться поток Thread-0. Также метод join() приостанавливает поток main.
2. Если бы main не спал Thread.sleep(500), то, чтобы Thread-1 выполнился первым - до main, нужно было бы вызвать метод join() и у него thread1.join().
3. Но main спит 0,5 секунды, поэтому Thread-1 успевает за это время сработать (метод join() для него не нужен).
. - Метод sleep(2000) в цикле для потоков позволяет соблюсти очередность их выполнения.
Создание объекта Runnable с использованием анонимного класса
Как было рассмотрено выше, второй метод создания потоков в Java подразумевает использование класса, который имплементирует интерфейс Runnable.
Но есть и другой вариант использования интерфейса Runnable для создания потоков: не создавать класс, который имплементирует интерфейс Runnable, а сразу создать объект Runnable, при создании нового потока.
При этом объект Runnable создается с использованием анонимного класса.
Java-код. Пример 3.0
public class CreateThread3 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() { // создание объекта Runnable и переопределение run()
@Override
public void run() {
System.out.println("Привет. Я поток " + Thread.currentThread().getName());
}
};
Thread thread = new Thread(runnable); // Создание нового потока с передачей в конструктор
// класса Thread объекта runnable
Thread thread1 = new Thread(runnable);
thread.start(); // Thread-0
thread.join(); // Thread-1 запускается после выполнения Thread-0
thread1.start(); // Thread-1
for (int i = 0; i < 10; i++) { // Thread-2 . . . Thread-11
Thread.sleep(500);
new Thread(runnable).start();
}
}
} // -- level_11/
Результат в консоли
Привет. Я поток Thread-0
Привет. Я поток Thread-1
Привет. Я поток Thread-2
Привет. Я поток Thread-3
Привет. Я поток Thread-4
Привет. Я поток Thread-5
Привет. Я поток Thread-6
Привет. Я поток Thread-7
Привет. Я поток Thread-8
Привет. Я поток Thread-9
Привет. Я поток Thread-10
Привет. Я поток Thread-11
В примере создаётся объект типа Runnable с помощью анонимного класса, который реализует метод run(). Затем создаётся объект Thread, ему передаётся объект runnable в качестве параметра конструктора, и запускается поток.
Важно отметить, что анонимный класс может использоваться только один раз и не может быть повторно использован в других местах программы. Если нужно использовать реализацию интерфейса или класса-родителя несколько раз, лучше создать отдельный класс для этого.
В конструктор Thread передаем создание объекта Runnable
В примере выше сначала создавался объект Runnable, в конструктор класса Thread .
Можно сделать так: в конструктор класса Thread передать создание объекта Runnable.
Java-код. Пример 3.1
public class CreateThread3_1 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() { // 1.1 Отдельно СОЗДАЕМ объект Runnable
@Override
public void run() {
System.out.println("Привет. Я поток " + Thread.currentThread().getName());
}
};
// 1.2 Создание нового потока с передачей в конструктор класса Thread объекта runnable. Ниже это сделано в цикле
Thread thread = new Thread(runnable);
thread.start(); // Thread-0
// --------------------------------------------------------------------------------------------------------------
// 2. СРАЗУ при создании потока в КОНСТРУКТОР класса Thread передаем СОЗДАНИЕ объекта Runnable
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Привет. Я поток " + Thread.currentThread().getName() + ". И я создан иначе!");
}
}).start(); // И сразу запускаем поток Thread-1
// --------------------------------------------------------------------------------------------------------------
for (int i = 0; i < 10; i++) { // Thread-2 . . . Thread-11
Thread.sleep(500);
new Thread(runnable).start();
}
}
} // -- level_11/
Результат в консоли
Привет. Я поток Thread-0
Привет. Я поток Thread-1. И я создан иначе!
Привет. Я поток Thread-2
Привет. Я поток Thread-3
Привет. Я поток Thread-4
Привет. Я поток Thread-5
Привет. Я поток Thread-6
Привет. Я поток Thread-7
Привет. Я поток Thread-8
Привет. Я поток Thread-9
Привет. Я поток Thread-10
Привет. Я поток Thread-11
Определяем поведение потока с помощью лямбда-выражения
Создание объекта Runnable и переопределение метода run() можно записать короче, используя лямбда-выражение.
Лямбда-выражения в Java - это отдельная тема, однако здесь мы увидим пример того, как создать объект Runnable с помощью лямбда-выражения.
Java-код
public class CreateThread3_3 {
public static void main(String[] args) throws InterruptedException {
Runnable t = new Runnable() { // Создаем экземпляр Runnable как обычно
@Override
public void run() { // Переопределяем метод run() - код, выполняющийся при запуске потока
System.out.println("Привет. Я поток 't' " + Thread.currentThread().getName());
}
};
// Аналогичный код с использованием лямбда-выражения
Runnable l = () -> System.out.println("Привет. Я поток 'l' " + Thread.currentThread().getName());
Thread thread = new Thread(t); // Создаем поток: в конструктор передаем объект t, реализующий интерфейс Runnable
thread.start(); // Запускаем поток
thread.join(); // Этот поток выполняется первым
// Создаем и запускаем поток в цикле: в конструктор передаем объект l, реализующий интерфейс Runnable
for (int i = 0; i < 5; i++) {
new Thread(l).start();
}
}
}
Результат в консоли
Привет. Я поток 't' Thread-0
Привет. Я поток 'l' Thread-1
Привет. Я поток 'l' Thread-2
Привет. Я поток 'l' Thread-4
Привет. Я поток 'l' Thread-5
Привет. Я поток 'l' Thread-3
- Сначала мы создаем экземпляр Runnable как обычно Runnable t = new Runnable(), переопределяем метод run(), создаем и запускаем поток.
- Затем определяем поведение потока с помощью лямбда-выражения Runnable l = () ->.
При этом так же создаётся экземпляр Runnable. Метод run() явно не указывается, но также переопределяется. Далее создаётся новый поток (в данном случае в цикле) и запускается как обычно.
Для создания объекта Runnable с помощью лямбда-выражения в Java необходимо:
1. Создать ссылку на интерфейс Runnable и написать лямбда-выражение для метода run().
2. Создать объект класса Thread, передав созданную ссылку на выполняемый интерфейс.
3. Вызвать метод start() для запуска потока.
Создание и запуск потока с использованием лямбда-выражения
В примере выше создавался экземпляр класса Runnable и с помощью лямбда-выражения. Затем этот экземпляр передавался в конструктор класса Thread при создании потока.
В примере ниже лямбда-выражение используется для создания и запуск потока. Естественно, одновременно с этим создается экземпляр класса Runnable и . Все умещается в одну строку.
Java-код. Пример 3.3
public class CreateThread3_3 {
public static void main(String[] args) throws InterruptedException {
// СРАЗУ при создании потока в КОНСТРУКТОР класса Thread передаем СОЗДАНИЕ объекта Runnable
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hi " + Thread.currentThread().getName());
}
}).start(); // Запуск потока
Thread.sleep(500);
// Создание и запуск потока с использованием лямбда-выражения
new Thread(() -> System.out.println("Hi Hi " + Thread.currentThread().getName())).start();
// Тот же код в цикле
for (int i = 0; i < 5; i++) {
new Thread(() -> System.out.println("Hello " + Thread.currentThread().getName())).start();
}
}
}
Результат в консоли
Hi Thread-0
Hi Hi Thread-1
Hello Thread-3
Hello Thread-4
Hello Thread-5
Hello Thread-6
Hello Thread-2
P.S.
Добрый день. Я изучаю JAVA. Некоторые темы мне довольно сложно освоить и усвоить. Поэтому я пишу такие вот статья с примерами. Делаю я это в большей степени для себя. Так мне проще понять и запомнить. Если кому-то пригодится – буду рад.
Этот материал используется исключительно в учебных целях и никакой коммерческой выгоды не несет. Текст частично взят из нейросети Яндекса, частично написан мною – материал берется из разных источников. Код писал сам, но за основу опять же брал какие-то уже существующие данные.
Если я где-то ошибся, что-то не правильно/не так описал – это нормально. Во-первых, я учусь, во-вторых, ни на что не претендую. Просьба это понимать.