0

I'm working on a program that needs to inspect multiple resources in parallel and periodically:

public class JobRunner {

    private final SensorService sensorService;
    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());

    public void run() {
        sensorService.finalAll().forEach(sensor -> {
            Runnable task = () -> {
                // read and save new data to log
                List<Double> data = sensor.fetchNewData();
                this.save(data);
            };

            // execute every 10 sec
            executor.scheduleWithFixedDelay(task, 0, 10, TimeUnit.SECONDS);
        });
    }

    public void save(List<Double> data) {
        // ...
    }
}

The findAll call returns a list of about 50 sensors, but when I run the program I see that while all sensors are queried on the first period, only 2-3 are called on subsequent executions (e.g - at 20 sec, 30 sec, etc). I'm thinking that since some sensors return faster than others, they complete the task's waiting cycle earlier and are grabbed by the next thread in the pool, thereby starving the other tasks that are slower to finish.

How can I ensure all tasks (sensors) are given equal treatment? What's are some best practices here; should I use a job queue or a different concurrency mechanism? Thanks.

sa125
  • 28,121
  • 38
  • 111
  • 153

1 Answers1

1

In your code there are N=count service.findAll() timers, which makes debugging and testing more difficult. Moreover there is no guarantee that old task will be executed and not overtaken by the new one in reasonable time. What if you

  1. Use single timer which triggers sensors check 10s after last all sensors check completed
  2. Go through sensors concurrently when check is triggered by the timer

Please, see the next code as an example. It prints 50 integers every 10 seconds and EOL afterwards. Parallelism is achieved with Stream API

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
        IntStream.range(0, 50).parallel().forEach(i -> System.out.print(i + " "));
        System.out.println();
    }
}, 0, 10, TimeUnit.SECONDS);

You may replace ScheduledExecutorService with Timer to make code clearer. And, as an option, instead of using parallel streams you can use another ExecutorService, submitting next N tasks to it on Timer and waiting until they are completed:

ExecutorService workerExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        List<Future<Void>> futures = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            final int index = i;
            Future<Void> future = workerExecutor.submit(new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    System.out.print(index + " ");
                    return null;
                }
            });
            futures.add(future);
        }
        for (Future<Void> future : futures) {
            try {
                future.get();
            } catch (InterruptedException|ExecutionException e) {
                throw new RuntimeException();
            }
        }
        System.out.println();
    }
}, 0, 10_000);
Anton Dovzhenko
  • 2,399
  • 11
  • 16