-1

I need to achieve the following functionality using multithreading/parallel processing using Java 7

List<String> permissionsList = new ArrayList<String>();
for (Integer site : getSites()) {
    String perm = authenticationService.getUserPermissionsForSite(site);
    permissionsList.add(perm);
}

The call getUserPermissionsForSite is a web service call. After the end of the for loop, the permissionsList should be fully populated with all the values.

What would be the best way to achieve it?

GSS
  • 3
  • 1
  • 5
  • Create a Future task and call the ` authenticationService.getUserPermissionsForSite(site); ` inside the task and once you get the result back add it to permissionsList. Using one of the pool implementation of Executors. – Lyju I Edwinson Sep 06 '17 at 23:02
  • 1
    This is a very broad question - there are many ways to accomplish this, each with advantages and disadvantages. Also note that you need to make sure that the operation you want to exacute in parallel (`getUserPermissionsForSite(site)`) is actually thread safe. – Hulk Sep 06 '17 at 23:05
  • @markspace note the "java 7" in the title... – Hulk Sep 06 '17 at 23:06
  • Good point. `ArrayList.add()` is certainly not thread safe, for starters. – markspace Sep 06 '17 at 23:06
  • 1
    As @LyjuIEdwinson suggested, I'd start by reading the docs about the [Executors](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html) - you should find lots of useful building blocks there. Also see [the official tutorial for concurrency](https://docs.oracle.com/javase/tutorial/essential/concurrency/) – Hulk Sep 06 '17 at 23:15
  • Possible duplicate of ["Parallel.For" for Java?](https://stackoverflow.com/questions/4010185/parallel-for-for-java) – Hulk Sep 07 '17 at 07:16
  • Also related: https://stackoverflow.com/q/34440604/2513200 – Hulk Sep 07 '17 at 07:32
  • https://stackoverflow.com/questions/4010185/parallel-for-for-java/46760218#46760218 – Crammeur Oct 15 '17 at 23:37

2 Answers2

3

This is how it would typically be done in Java 7. A CountDownLatch is used for waiting till all the sites are called.

    int numOfThreads = 5;
    List<String> permissionsList = Collections.synchronizedList(new ArrayList<>());
    List<Integer> sites = getSites();
    CountDownLatch countDownLatch = new CountDownLatch(sites.size());
    ExecutorService es = Executors.newFixedThreadPool(numOfThreads);
    for (Integer site : sites) {
        es.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    String perm = authenticationService.getUserPermissionsForSite(site);
                    permissionsList.add(perm);
                } finally {
                    countDownLatch.countDown();
                }
            }
        });
    }
    countDownLatch.await();
Jose Martinez
  • 11,452
  • 7
  • 53
  • 68
1

You can use ExecutorService.invokeAll() to schedule all your requests at once. You can specify a timeout to avoid waiting indefinitely. Upon return, all requests will have completed, faulted, or timed out. Iterate over the tasks and fetch the results, and handle timeouts and partial failures however you like. Note that if you add a timeout, you may want to scale it by the number of requests.

You'll need to decide what degree of parallelism you want. My example will create up to as many threads as there are processors available to run them, but the thread count won't exceed the number of requests. That's pretty aggressive; you could probably get away with fewer threads.

List<String> getPermissions(final List<Integer> sites) throws InterruptedException {
    if (sites.isEmpty()) {
        return Collections.emptyList();
    }

    final List<String> permissions = new ArrayList<>();
    final List<Callable<String>> tasks = new ArrayList<>();

    final int threadCount = Math.min(
        Runtime.getRuntime().availableProcessors(),
        sites.size()
    );

    final ExecutorService executor = Executors.newFixedThreadPool(threadCount);

    try {
        for (final Integer site : sites) {
            tasks.add(
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return authenticationService.getUserPermissionsForSite(site);
                    }
                }
            );
        }

        for (final Future<String> f : executor.invokeAll(tasks/*, timeout? */)) {
            try {
                // If you added a timeout, you can check f.isCancelled().
                // If timed out, get() will throw a CancellationException.
                permissions.add(f.get());
            }
            catch (final ExecutionException e) {
                // This request failed.  Handle it however you like.
            }
        }
    }
    finally {
        executor.shutdown();
    }

    return permissions;
}

Like most Java operations that perform a wait, invokeAll may throw an InterruptedException. It's up to you whether you want to catch it or let it propagate.

Mike Strobel
  • 25,075
  • 57
  • 69