Java: создайте фоновый поток для обновления переменной

* новичок в Java

в Main он вызывает processItems() перед итерацией по workItems. Проблема в том, что processItems() занимает много времени, скажем, 10 минут.

 import java.lang.*;
 import java.util.*;
 import static java.util.stream.Collectors.*;

 public class App {
     private List<String> workItems;
     private int factor = 0;
     public App() {
         workItems = new ArrayList<>();             
         //create some work in workItmes
         workItems.add("apple");
         workItems.add("oranges");
         workItems.add("bananas");
     }

     public List<String> getWorkItems() {
         return workItems;
     }   

     public void calculateFactor() throws Exception{
         // does some work which takes a long time
         Thread.sleep(5*1000);
         factor = 1;
     }   

     public boolean evaluateItem(String item, int factor) {
         // do some work based on item and current factor 
         System.out.println("Received: " + item + " " + factor);
         return true;
     }   

     public int getFactor() {
         return factor;
     }   

     public static void main(String[] args) throws Exception
     {
         App myApp = new App();
         System.out.println("starting...");
         int cnt = 0;
         while (true) {
             cnt++;
             if (cnt > 5) {
                 break;
             }   

             myApp.calculateFactor();    //<------- I dont want to call this here but in a background thread
             int current_factor = myApp.getFactor();
             List<Boolean> results = myApp.getWorkItems().stream().map(s -> myApp.evaluateItem(s, current_factor)).collect(toList());
             System.out.println(results);
             Thread.sleep(1*1000); // sleep a min
         }
         System.out.println("done");
     }
 }

Я хотел бы передать вызов, myApp.calculateFactor(), в основном фоновому потоку

Две вещи: этот фоновый поток должен иметь доступ к workItems и обновлять переменную, которая видна основному через getFactor()

Я смотрел на ExecutorService executorService= Executors.newSingleThreadExecutor();, но ни один из руководств, которые я, кажется, не предполагает, что он может видеть пространство имен родителя потока.

Кроме того, как будет выглядеть очистка фонового потока? просто вызвать shutdown()? как он обрабатывает прерывания?

Где используется processItems() в main? Объект приложения не создан. Итак, как это используется в примере?

Master Chief 03.04.2019 09:46

@MasterChief обновил код

ealeon 03.04.2019 09:51

В основном методе вы заняты ожиданием while(true) { cnt++ ...Thread.sleep(1*1000)...}. Вы добавили это только для ожидания метода calculateFactor? Или по другой причине?

Julien 03.04.2019 10:01

@Julien по другой причине, чтобы легко увидеть, что я ожидаю, что main завершится через 5 секунд (cnt равно 5 * 1 секунде) вместо 30 секунд, потому что дополнительные 5 секунд на итерацию от calcualteFactor, если calculateFactor происходит в фоновом потоке

ealeon 03.04.2019 10:08
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
546
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

java.lang.Thread В JavaDoc есть пример запуска системного потока. И JVM будет делать GC. Object.wait(), Thread.sleep(..) .. иметь возможность получать сигнал системного прерывания. Будьте осторожны с одновременным доступом к данным, приятно видеть volatile использование, чтобы предотвратить непоследовательное использование памяти. Ниже из JavaDoc

Например, поток, вычисляющий простые числа, превышающие заданное значение, можно записать следующим образом:

class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

Затем следующий код создаст поток и запустит его выполнение:

PrimeThread p = new PrimeThread(143);
p.start();

И Executors.newSingleThreadExecutor() просто создайте ThreadPool только с 1 рабочим потоком с LinkedBlockingQueue очередью заданий. Используйте его как шаблон Singleton. Обычно дайте крючок сделать shutdown() после того, как сервер не работает.

Я думаю, что у меня это работает следующим образом, но по какой-то причине дочерний поток зависает даже после вызова shutdown() для исполнителя

     private ExecutorService executor;
     public App() {
         workItems = new ArrayList<>();
         //create some work in workItmes
         workItems.add("apple");
         workItems.add("oranges");
         workItems.add("bananas");

         executor = Executors.newSingleThreadExecutor();
         executor.submit(() -> {
             while (true) {
                 //Stopwatch stopwatch = Stopwatch.createStarted();

                 String threadName = Thread.currentThread().getName();
                 System.out.println("Hello " + threadName);
                 System.out.println("FROM THREAD: " + this.getWorkItems());
                 //this.incrementFactor(1);
                 try {
                     this.calculateFactor();
                 } catch (Exception e) {
                     //do nothing
                 }
                 try {
                     Thread.sleep(1*1000);
                 } catch (InterruptedException e) {
                     break;
                 }

             }
         });

     }

Это потому, что вы никогда не выходите из цикла while(true). Оператор break во время Thread.sleep(1*100) никогда не достигается (он достигается только в случае прерывания (например, ctrl-c))

Julien 03.04.2019 11:30

Прерывание @Julien произойдет только в том случае, если метод interrupt() вызывается в потоке. CTRL+C не вызовет прерывания.

Holger 03.04.2019 13:06

@Holger хорошо, плохо. Я думал, что это так

Julien 03.04.2019 13:11
Ответ принят как подходящий

Вы действительно можете использовать Executors.newSingleThreadExecutor() следующим образом:

public class App {
    private List<String> workItems;
    private AtomicInteger factor = new AtomicInteger(0);

    public App() {
        workItems = new ArrayList<>();
        //create some work in workItmes
        workItems.add("apple");
        workItems.add("oranges");
        workItems.add("bananas");
    }

    public List<String> getWorkItems() {
        return workItems;
    }


    public void calculateFactor() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        factor.set(1);
    }

    public boolean evaluateItem(String item, int factor) {
        // do some work based on item and current factor
        System.out.println("Received: " + item + " " + factor);
        return true;
    }

    public AtomicInteger getFactor() {
        return factor;
    }

    public static void main(String[] args) throws Exception {
        App myApp = new App();
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        System.out.println("starting...");
        int cnt = 0;
        while (true) {
            cnt++;
            if (cnt > 5) {
                break;
            }

            executorService.submit(() -> {
                try {
                    myApp.calculateFactor();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("Error when calculating Factor");
                }
            });
            int currentFactor = myApp.getFactor().get();
            List<Boolean> results = myApp.getWorkItems().stream().map(s -> myApp.evaluateItem(s, currentFactor)).collect(toList());
            System.out.println(results);
            TimeUnit.SECONDS.sleep(1);
        }

        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.SECONDS);
        System.out.println("done");
    }
}

Здесь executorService запустит новый Runnable, который просто запустит ваш метод calculateFactor. Чтобы убедиться, что фактор будет правильно обновлен и прочитан mainThread параллельно, вы можете использовать AtomicInteger, предназначенный для такого рода заданий. О завершении работы и прерывании, в конце вы должны сделать:

executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.SECONDS); // Time is arbitrary here. It depends your need

Подробнее см. shutdown и awaitTermination, какой первый вызов имеет какую-либо разницу?.

В вашем примере, когда я его тестирую, factor не меняется, потому что вы ждете 5 секунд, а поток calculateFactor ждет 5 секунд, поэтому мой результат:

starting...
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
done

Но если я поставлю, скажем, cnt> 10, у меня будет такой результат:

starting...
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 0
Received: oranges 0
Received: bananas 0
[true, true, true]
Received: apple 1
Received: oranges 1
Received: bananas 1
[true, true, true]
Received: apple 1
Received: oranges 1
Received: bananas 1
[true, true, true]
Received: apple 1
Received: oranges 1
Received: bananas 1
[true, true, true]
Received: apple 1
Received: oranges 1
Received: bananas 1
[true, true, true]
Received: apple 1
Received: oranges 1
Received: bananas 1
[true, true, true]

Надеюсь, это ответ на ваш вопрос

Добавление :, если вы хотите запустить calculateFactor только один раз, вы можете использовать защелка для ожидания в потоке. Пример :

public class App {
    private List<String> workItems;
    private AtomicInteger factor = new AtomicInteger(0);
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public App() {
        workItems = new ArrayList<>();
        //create some work in workItmes
        workItems.add("apple");
        workItems.add("oranges");
        workItems.add("bananas");
    }

    public List<String> getWorkItems() {
        return workItems;
    }

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public void calculateFactor() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        factor.set(1);
        countDownLatch.countDown();
    }

    public boolean evaluateItem(String item, int factor) {
        // do some work based on item and current factor
        System.out.println("Received: " + item + " " + factor);
        return true;
    }

    public AtomicInteger getFactor() {
        return factor;
    }

    public static void main(String[] args) throws Exception {
        App myApp = new App();
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        System.out.println("starting...");

        executorService.submit(() -> {
            try {
                myApp.calculateFactor();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Error when calculating Factor");
            }
        });
        myApp.getCountDownLatch().await();
        int currentFactor = myApp.getFactor().get();
        List<Boolean> results = myApp.getWorkItems().stream().map(s -> myApp.evaluateItem(s, currentFactor)).collect(toList());
        System.out.println(results);


        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.SECONDS);
        System.out.println("done");
    }
}

Выход будет:

starting...
Received: apple 1
Received: oranges 1
Received: bananas 1
[true, true, true]
done

Подробнее о CountDownlLatch: https://www.baeldung.com/java-countdown-защелка

Другие вопросы по теме