ExecutorService. Как обратиться к переменной из потока?

Рейтинг: 2Ответов: 2Опубликовано: 26.05.2023

Есть следующий минимально воспроизводимый пример:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        int done = 0;
        for (int i = 0; i < 5; i++) {
            executorService.submit((Runnable) () -> {
                System.out.println("Doing something...");
                done++;
            });
        }
        executorService.shutdown();
        System.out.println(done);
    }
    
}

Как посчитать не количество итераций, а именно количество завершенных потоков (submit-ов)? Пытаюсь сделать это с помощью переменной done, но получаю следующее исключение:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: Local variable done defined in an enclosing scope must be final or effectively final

Как решить данную проблему?

Ответы

▲ 3Принят

Как подсказано в комментариях, для соблюдения указанного требования следует использовать AtomicInteger в качестве контейнера значения и его методы getAndIncrement() / get(), либо использовать статическую переменную (на уровне класса), или же объявить локальную переменную как массив:

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class MyClass {
    
    static int done = 0;
    
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        int[] arr = {0};
        AtomicInteger at = new AtomicInteger(0);
        for (int i = 0; i < 5; i++) {
            executorService.submit((Runnable) () -> {
                System.out.println("Thread " + Thread.currentThread().getName() + " done, values changed: atomic=" + at.getAndIncrement() + "; static=" + done++ + "; arr=" + arr[0]++);
            });
        }
        executorService.shutdown();
        System.out.println("Main thread '" + Thread.currentThread().getName() + "': atomic: " + at.get() + "; static: " + done + "; arr: " + arr[0]);
    }
}

Результаты:

Thread pool-1-thread-1 done, values changed: atomic=0; static=0; arr=0
Main thread 'main': atomic: 0; static: 0; arr: 0
Thread pool-1-thread-1 done, values changed: atomic=1; static=1; arr=1
Thread pool-1-thread-1 done, values changed: atomic=2; static=2; arr=2
Thread pool-1-thread-1 done, values changed: atomic=3; static=3; arr=3
Thread pool-1-thread-1 done, values changed: atomic=4; static=4; arr=4

При такой реализации главный поток завершает выполнение раньше, чем успеют завершиться потоки, запущенные при помощи ExecutorService.


Если нужно, чтобы главный поток main дождался окончания остальных потоков, следует использовать ExecutorService::awaitTermination:

public class MyClass {
    static int done = 0;
    
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        int[] arr = {0};
        AtomicInteger at = new AtomicInteger(0);
        for (int i = 0; i < 5; i++) {
            executorService.submit((Runnable) () -> {
                System.out.println("Thread " + Thread.currentThread().getName() + " done, values changed: atomic=" + at.getAndIncrement() + "; static=" + done++ + "; arr=" + arr[0]++);
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(100L, TimeUnit.MILLISECONDS);
        System.out.println("Main thread '" + Thread.currentThread().getName() + "': atomic: " + at.get() + "; static: " + done + "; arr: " + arr[0]);
    }
}
Thread pool-1-thread-1 done, values changed: atomic=0; static=0; arr=0
Thread pool-1-thread-1 done, values changed: atomic=1; static=1; arr=1
Thread pool-1-thread-1 done, values changed: atomic=2; static=2; arr=2
Thread pool-1-thread-1 done, values changed: atomic=3; static=3; arr=3
Thread pool-1-thread-1 done, values changed: atomic=4; static=4; arr=4
Main thread 'main': atomic: 5; static: 5; arr: 5
▲ 3

Я бы предложил ещё вариант - поместить все Future в список и в нужном месте посчитать, сколько из них завершилось.

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        List<Future<?>> futures = new LinkedList<>();
        for (int i = 0; i < 5; i++) {
            Future<?> future = executorService.submit((Runnable) () -> {
                System.out.println("Doing something...");
            });
            futures.add(future);
        }

        executorService.shutdown();
        long done = futures.stream().filter(Future::isDone).count();
        System.out.println(done);
    }