Как выполнить код в отдельном потоке в Java?
Tags: java, concurrency, interview
Published: 2024-05-08 10:14:04

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

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

```java
public class MyThread extends Thread {
    public void run() {
       .....
    }
}
```
Для запуска нашего потока нужно создать инстанс класса и вызвать метод start():

```java
MyThread myThread = new MyThread();
myTread.start();
```
Также можно создать анонимный класс, наследующий класс Thread:

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

myThread.start();
```

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

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

Thread thread = new Thread(myRunnable);
thread.start();
```
Аналогично, вместо явного объявления класса, который реализует интерфейс Runnable, можно создать анонимный класс:

```java
Runnable myRunnable = new Runnable() {
    public void run(){
        .....
    }
};
...
Thread thread = new Thread(myRunnable);
thread.start();
```
Или используя lambda:
```java
Runnable myRunnable = () -> {.....};
...
Thread thread = new Thread(myRunnable);
thread.start();
```

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

```java
Executor executor = Executors.newCachedThreadPool();
```
И вызвать метод execute, в который нужно передать наш Runnable:

```java
Runnable myRunnable = new Runnable() {
    public void run() {
        .....
    }
};
executor.execute(myRunnable);
```
Существует четыре основных Thread Pool, которые можно использовать:

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

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

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

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

```java
ExecutorService executor = Executors.newCachedThreadPool();
Future<List<String>> future = executor.submit(myCallable);
try {
    List<String> result = future.get();
} catch (InterruptedException e) {
    ....
} catch (ExecutionException e) {
    ....
}
```
Метод submit вернет в качестве результата Future. Для получения результата, нужно вызвать метод get. Этот метод блокирующий, вызывающий поток будет ожидать, потока результат станет доступным.

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

```java
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) {
        ......
    }
}

```

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