DEV Community

faangmaster
faangmaster

Posted on • Edited on

1

Как выполнить код в отдельном потоке в Java?

В Java существует несколько способов выполнить код/задачу в отдельном потоке.

Наследование класса Thread

Первый способ - это создать свой класс, который наследуется от класса Thread.
Например,

public class MyThread extends Thread {
    public void run() {
       .....
    }
}
Enter fullscreen mode Exit fullscreen mode

Для запуска нашего потока нужно создать инстанс класса и вызвать метод start():

MyThread myThread = new MyThread();
myTread.start();
Enter fullscreen mode Exit fullscreen mode

Также можно создать анонимный класс, наследующий класс Thread:

Thread myThread = new Thread() {
    public void run() {
        .....
    }
}

myThread.start();
Enter fullscreen mode Exit fullscreen mode

Имплиментировать интерфейс Runnable

Для этого нужно объявить класс, который реализует интерфейс Runnablе. Далее создать инстанс этого класса и передать его в конструктор объекта класса Thread и вызвать метод start():

public class MyRunnable implements Runnable {
    public void run() {
       .....
    }
}
....
Runnable myRunnable = new MyRunnable();

Thread thread = new Thread(myRunnable);
thread.start();
Enter fullscreen mode Exit fullscreen mode

Аналогично, вместо явного объявления класса, который реализует интерфейс Runnable, можно создать анонимный класс:

Runnable myRunnable = new Runnable() {
    public void run(){
        .....
    }
};
...
Thread thread = new Thread(myRunnable);
thread.start();
Enter fullscreen mode Exit fullscreen mode

Или используя lambda:

Runnable myRunnable = () -> {.....};
...
Thread thread = new Thread(myRunnable);
thread.start();
Enter fullscreen mode Exit fullscreen mode

Использование Executor

Вместо явного создания потока, задачу можно выполнить используя Executor framework. Для этого нужно создать Thread Pool:

Executor executor = Executors.newCachedThreadPool();
Enter fullscreen mode Exit fullscreen mode

И вызвать метод execute, в который нужно передать наш Runnable:

Runnable myRunnable = new Runnable() {
    public void run() {
        .....
    }
};
executor.execute(myRunnable);
Enter fullscreen mode Exit fullscreen mode

Существует четыре основных Thread Pool, которые можно использовать:

newFixedThreadPool - создает новые потоки, по мере сабмита тасок, вплоть до максимального размера пула. Далее поддерживает размер пула постоянным. Если поток упадет из-за unexpected Exception, то создаст новый поток.
newCachedThreadPool - Если потоки не используются(idle), может их убивать. Если же число задач увеличивается, то создает новые потоки. При этом не имеет верхнего предела по числу потоков.
newSingleThreadExcutor - создает всего один поток. Если он падает, то создает новый. Гарантирует выполнение задач последовательно.
newScheduledThreadPool - Пул фиксированного размера. Поддерживает выполение отложенных и периодических задач по рассписанию.

Callable, Future и ExecutorService

Как вы успели заметить, Runnable имеет один метод - run, который не возращает никакого результата (void). Если нам надо, чтобы наша задача/код, выполняемая в отдельном потоке, вернула какой-то результат - мы можем использовать Callable.
Объявим класс (анонимный), который реализует Callable:

Callable myCallable = new Callable<List<String>>() {
    public List<String> call() throws Exception {
        ........
        return result;
    }
};

Enter fullscreen mode Exit fullscreen mode

Создадим ExecutorService и вызовем метод submit, вместо execute:

ExecutorService executor = Executors.newCachedThreadPool();
Future<List<String>> future = executor.submit(myCallable);
try {
    List<String> result = future.get();
} catch (InterruptedException e) {
    ....
} catch (ExecutionException e) {
    ....
}
Enter fullscreen mode Exit fullscreen mode

Метод submit вернет в качестве результата Future. Для получения результата, нужно вызвать метод get. Этот метод блокирующий, вызывающий поток будет ожидать, потока результат станет доступным.

CompletionService

Если мы хотим выполнить множество задач, и результаты получать в порядке их доступности, то можно использовать CompletionService.
Для этого нужно обернуть ExecutorService в ExecutorCompletionService:

ExecutorService executor = Executors.newCachedThreadPool();
CompletionService<List<String>> completionService = new ExecutorCompletionService<>(executor);
for (....) {
    completionService.submit(myCallable);
}
for (...) {
    try {
        Future<List<String>> future = completionService.take();
        List<String> result = future.get();
    } catch (InterruptedException e) {
        .....
    } catch (ExecutionException e) {
        ......
    }
}

Enter fullscreen mode Exit fullscreen mode

С будущих статьях также расскажу про CompletableFuture и виртуальные потоки.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More