Runnable.run: примеры (JAVA)
Runnable.run: voidОписание метода run
Метод run принадлежит интерфейсу java.lang.Runnable и имеет сигнатуру public void run(). Аргументов у метода нет, возвращаемое значение - void. Метод не объявляет проверяемых исключений (checked exceptions), поэтому в реализации либо обрабатываются все проверяемые исключения, либо они не используются.
Типичное назначение метода - размещение тела задачи, которое может выполняться в потоке. Варианты вызова:
- Вызов напрямую: выполнение происходит в текущем потоке.
- Передача экземпляра в
Threadи вызовstart(): выполнение происходит в отдельном потоке. - Передача в API из пакета
java.util.concurrent(например,ExecutorService): задача выполняется одним из потоков пула.
Особенности поведения:
- При выбросе непроверяемого исключения (RuntimeException или Error) оно приводит к завершению выполняющего потока; в пуле потоков исключение обычно логируется и работа пула продолжается.
- Интерфейс
Runnableявляется функциональным интерфейсом, поэтому реализацию можно задать через лямбду или ссылку на метод (начиная с Java 8). - Вызов
run()напрямую не создает новый поток.
Аргументы: отсутствуют. Возвращаемое значение: отсутствует (void). Исключения: метод не декларирует проверяемых исключений; unchecked-исключения распространяются в вызывающий поток.
Короткие примеры использования
Примеры показывают разные способы запуска логики, представленной через Runnable, и визуальную разницу между вызовом run() и запуском через Thread.start() либо ExecutorService.
1) Прямой вызов run() и запуск через Thread:
public class RunExample1 {
public static void main(String[] args) {
Runnable r = () -> System.out.println("Выполнение в потоке: " + Thread.currentThread().getName());
r.run(); // выполняется в текущем (main)
new Thread(r, "worker-1").start(); // выполняется в новом потоке
}
}
Выполнение в потоке: main Выполнение в потоке: worker-1
2) Передача в ExecutorService и получение Future (результат null для Runnable):
import java.util.concurrent.*;
public class RunExample2 {
public static void main(String[] args) throws Exception {
ExecutorService ex = Executors.newSingleThreadExecutor();
Runnable r = () -> System.out.println("Task in " + Thread.currentThread().getName());
Future<?> f = ex.submit(r);
System.out.println("Future get: " + f.get());
ex.shutdown();
}
}
Task in pool-1-thread-1 Future get: null
3) Лямбда и обработка unchecked-исключения внутри run:
public class RunExample3 {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println("Начало");
throw new RuntimeException("Ошибка в задаче");
};
try {
r.run();
} catch (RuntimeException ex) {
System.out.println("Поймано: " + ex.getMessage());
}
}
}
Начало Поймано: Ошибка в задаче
Похожие API в Java и их особенности
- Callable<V> - похожая роль, но возвращает значение или выбрасывает проверяемое исключение. Предпочтительна при необходимости результата.
- FutureTask<V> / RunnableFuture - комбинирует Runnable и Future, позволяет запускать и получить результат/исключение.
- Thread (унаследовать класс) - дает возможность расширить поведение потока, но композиция с Runnable обычно предпочтительнее.
- ForkJoinTask - специализирован для fork/join-парадигмы и рекурсивного распараллеливания; предпочтительнее при задачах, разбиваемых на подзадачи.
- CompletableFuture - удобен для асинхронных цепочек и композиции задач с результатами и обработкой ошибок.
Аналоги в других языках и отличия
Краткие примеры показывают, как в других языках задаётся логика, эквивалентная Java Runnable.run.
Python (threading) - целевая функция передаётся в Thread:
import threading
def task():
print('Выполняется в', threading.current_thread().name)
threading.Thread(target=task, name='py-worker').start()
print('main поток')
main поток Выполняется в Thread-1
C# (ThreadStart / Task) - делегат ThreadStart похож на Runnable; Task даёт удобства:
using System;
using System.Threading;
class Example {
static void Main() {
Thread t = new Thread(() => Console.WriteLine("Выполнение в " + Thread.CurrentThread.ManagedThreadId));
t.Start();
Console.WriteLine("main");
}
}
main Выполнение в 3
JavaScript - в однопоточном окружении нет прямого эквивалента; в браузере используется Web Worker для параллелизма, в Node.js - worker_threads. Часто применяется асинхронность через колбэки и промисы.
Go - горутины: вызов функции с go выполняет её параллельно, возвращаемого значения нет; модель отличается управлением планировщиком языка.
package main
import (
"fmt"
"time"
)
func main() {
go func() { fmt.Println("в горутине") }()
time.Sleep(10 * time.Millisecond)
fmt.Println("main")
}
в горутине main
Kotlin - использует те же Runnable/Thread; дополнительно есть корутины (kotlinx.coroutines) с более богатым API для асинхронности и отмены.
Lua - корутины кооперативные, работают иначе, не дают параллелизма на уровне ядра.
PHP - стандартно без потоков; расширение pthreads предоставляет похожую модель, но применяется редко в веб-среде.
Основное отличие от Java: формы запуска и модель памяти. В Java Runnable интегрирован с моделью потоков JVM и API java.util.concurrent; в других языках используются или системные потоки, или встроенные легковесные примитивы (горутины, корутины).
Типичные ошибки и примеры
Список распространённых ошибок и демонстрации их проявления.
1) Вызов run() вместо start() при попытке создать новый поток:
public class ErrorExample1 {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("Поток: " + Thread.currentThread().getName()));
t.run(); // НЕ запускает новый поток
System.out.println("После вызова run");
}
}
Поток: main После вызова run
Ожидание: вывод из отдельного потока. Фактически задача выполнилась в main.
2) Ожидание результата от Runnable при submit: результат null:
// аналогично примеру в разделе examples
// После submit(runnable).get() возвращается null, что иногда удивляет.
Future get: null
3) Неправильная обработка прерываний внутри run (игнорирование Thread.interrupt):
public class ErrorExample3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
// не проверяется interrupted, бесконечный цикл
}
});
t.start();
t.interrupt();
System.out.println("Попытка прерывания отправлена");
}
}
Попытка прерывания отправлена // поток может не завершиться, так как цикл не реагирует на прерывание
4) Гонка данных при доступе к общему состоянию без синхронизации:
public class ErrorExample4 {
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
for (int i = 0; i < 10000; i++) counter++;
};
Thread t1 = new Thread(r), t2 = new Thread(r);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(counter); // часто < 20000
}
}
Значение может быть отличным от 20000 из-за условий гонки
Изменения и эволюция
Сигнатура метода run() не менялась с ранних версий Java: это простой метод без аргументов и возвращаемого значения.
- Java 8:
Runnableпомечен как функциональный интерфейс, что позволило использовать лямбды и ссылки на методы. - Современные версии JVM и библиотеки: Project Loom (Java 19+ в виде предварительных сборок и далее) вводит виртуальные потоки, которые изменяют модель запуска потоков, но метод
run()и интерфейсRunnableостаются прежними. Разница - в характеристиках создаваемого потока (легковесные виртуальные потоки), а не в API Runnable. - Других изменений в самом интерфейсе не происходило; дополнительные утилиты и адаптеры появились в
java.util.concurrent.
Расширенные и нетипичные примеры
Несколько продвинутых приёмов с кодом и выводом.
1) Обёртка Runnable для централизованной обработки исключений и логирования:
import java.util.concurrent.*;
public class AdvExample1 {
static Runnable safe(Runnable r) {
return () -> {
try {
r.run();
} catch (Throwable t) {
System.err.println("Ошибка в задаче: " + t.getMessage());
}
};
}
public static void main(String[] args) {
ExecutorService ex = Executors.newSingleThreadExecutor();
ex.submit(safe(() -> { throw new RuntimeException("boom"); }));
ex.shutdown();
}
}
Ошибка в задаче: boom
2) Преобразование Runnable в Callable, чтобы вернуть значение после выполнения:
import java.util.concurrent.*;
public class AdvExample2 {
static Callable toCallable(Runnable r, T result) {
return () -> { r.run(); return result; };
}
public static void main(String[] args) throws Exception {
Callable c = toCallable(() -> System.out.println("Выполняется"), "готово");
System.out.println(c.call());
}
}
Выполняется готово
3) Планирование повторяющейся задачи и отмена через Future:
import java.util.concurrent.*;
public class AdvExample3 {
public static void main(String[] args) throws Exception {
ScheduledExecutorService sch = Executors.newSingleThreadScheduledExecutor();
Runnable r = () -> System.out.println("tick " + System.currentTimeMillis());
ScheduledFuture<?> f = sch.scheduleAtFixedRate(r, 0, 200, TimeUnit.MILLISECONDS);
Thread.sleep(650);
f.cancel(false);
sch.shutdown();
System.out.println("Cancelled");
}
}
tick 1610000000000 tick 1610000000200 tick 1610000000400 Cancelled
4) Комбинация Runnables: последовательное и параллельное исполнение в рамках пула:
import java.util.concurrent.*;
public class AdvExample4 {
static Runnable seq(Runnable a, Runnable b) {
return () -> { a.run(); b.run(); };
}
public static void main(String[] args) throws InterruptedException {
ExecutorService ex = Executors.newFixedThreadPool(2);
Runnable a = () -> System.out.println("A " + Thread.currentThread().getName());
Runnable b = () -> System.out.println("B " + Thread.currentThread().getName());
ex.submit(seq(a,b));
ex.shutdown();
ex.awaitTermination(1, TimeUnit.SECONDS);
}
}
A pool-1-thread-1 B pool-1-thread-1
5) Запуск на виртуальном потоке (Java с поддержкой Loom):
// Требуется Java с виртуальными потоками (Project Loom)
public class AdvExample5 {
public static void main(String[] args) {
Runnable r = () -> System.out.println("Виртуальный: " + Thread.currentThread());
Thread t = Thread.ofVirtual().unstarted(r);
t.start();
}
}
Виртуальный: Thread[VirtualThread-1,5,main]
6) Использование ThreadLocal внутри Runnable для сохранения контекста:
public class AdvExample6 {
static ThreadLocal ctx = ThreadLocal.withInitial(() -> "undef");
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
ctx.set("ctx-" + Thread.currentThread().getName());
System.out.println(ctx.get());
};
Thread t = new Thread(r, "t1");
t.start(); t.join();
}
}
ctx-t1
Каждый пример демонстрирует приёмы для повышения надёжности, гибкости и интеграции Runnable в современные конструкции Java.