Joro Seksa
Joro Seksa

Reputation: 1583

Return value from java method that performs asynchronous task

Currently i have a web interface where a user enters some data like start_date and end_date and clicks a button which makes an Ajax request to a servlet controler that executes a method that accpets the user entered data and performs something like this:

In a for loop along a list of values representing a database tables with the same structure there is a query and the additional computation is done with the resultset i.e.

for(int i=0; i<list.size(); i++)
{
    String dbTable = "vehicle_" + list.get(i).getTableName(); 

    Object afterComputationValue = someMethodThatQueriesDataAndReturnValue(dbTable);
} 

So the object afterComputationValue is then put into a list and this list need to be returned to the servlet, then serialized and returned to the user.

So the method that returns the list looks like this:

private List<Object> theMethod(String startDate, String endDate)
{
     List<Object> theList = new ArrayList<>();

     List<AnotherObject> databaseData = SomeClass.getVehiclesList();
     for(int i=0; i<list.size(); i++)
     {
        String dbTable = "vehicle_" + list.get(i).getTableName(); 
        Object afterComputationValue = someMethodThatQueriesDataAndReturnValue(dbTable);
        theList.put(afterComputationValue);
     }

     return theList; 
}

Unfortunatelly it takes more than a minute to execute this method so i was thinking that if i get advantage of the 8 core server it might work 8 times faster if the method is made make the computation of all the queries in a separate thread.

So if i am right about this i want to ask how to make this method returns value after all the threads (their size depends on the list size ) have completed their tasks?

Upvotes: 0

Views: 4249

Answers (4)

jeojavi
jeojavi

Reputation: 886

You can use ExecutorService. The code can be something like this (code not tested):

private List<Object> theMethod(String startDate, String endDate) {
    final List<Object> theList = Collections.synchronizedList(new ArrayList<>());
    final List<AnotherObject> databaseData = SomeClass.getVehiclesList();
    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
    for(int i=0; i<list.size(); i++) {
        final index = i;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                String dbTable = "vehicle_" + list.get(index).getTableName(); 
                ResultSet rs = someMethodThatReturnsResultSet(dbTable);
                Object afterComputationValue = anotherMethodToDoSomeComputation(rs); 
                theList.put(afterComputationValue);
            }
        })
    }
    executor.shutdown();
    try {
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } 
    catch (InterruptedException e) {
        // Do something
    }
    return theList; 
}

From comments:

Note that if one or more thread fail this method returns the list anyway. If that is not the expected behaviour you must 1) catch the Exception inside the run() method and treat it properly or 2) check if theList has the same size of the original list (if the sizes are different, something went wrong).

Upvotes: 2

Luiggi Mendoza
Luiggi Mendoza

Reputation: 85779

From the given code, we're not sure if the bottleneck is someMethodThatReturnsResultSet or some of the other methods like anotherMethodToDoSomeComputation.

Assuming that someMethodThatReturnsResultSet is the bottleneck, then maybe you have to tune the queries used or make the database calls through a single database connection, close the resources, connect to database using JNDI instead of naive Class.forName(...); DriverManager.getConnection(...), etc.

Assuming that anotherMethodToDoSomeComputation or SomeClass.getVehiclesList() or another of these is the bottleneck, then provide the code to analyze if its performance can improve using concurrency.

To spot which method is the bottleneck, don't trust in your current knowledge (unless you have lot of experience, and with this I mean having years of experience and probably gray hair), use a profiler like Visual VM or Java Mission Control.


From your comment:

The query to the database is slow!

Then you have to tune it up:

  • Change the order of the fields in WHERE clause
  • Check if you can add more indexes to database relevant to your query
  • Add more RAM to the database server
  • Check for specific tuning options on your database engine.

Note that using concurrency on a database query won't accelerate the process that much since, after all, is a disk read operation, which is slow by default. Also, the database engine will synchronize multiple requests if they are over the same table.

Upvotes: 1

Mak
Mak

Reputation: 616

You have 2 good options:

  1. You can use fork/join framework For further details check this link but you can find lots of sample code. http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

  2. You can use CompletionService: Here I am adding some sample code but this is just sample code and you need to tweek it as per your need.

    public class CompletionServiceExample {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            calculate();
        }
    
        private static List<Object> calculate() throws InterruptedException, ExecutionException {
            List<Object> theList = new ArrayList<>();
    
            final List<AnotherObject> vehiclesList = SomeClass.getVehiclesList();
            ExecutorService taskExecutor = Executors.newFixedThreadPool(vehiclesList.size());
            java.util.concurrent.CompletionService<ResultSet> rsCompletionService = new ExecutorCompletionService<>(taskExecutor);
            java.util.concurrent.CompletionService<Object> completionServiceForAnotherCalc = new ExecutorCompletionService<>(taskExecutor);
            for(int i=0; i< vehiclesList.size(); i++)
            {
                final String dbTable = "vehicle_" + vehiclesList.get(i).getTableName();
                rsCompletionService.submit(new Callable<ResultSet>() {
                    @Override
                    public ResultSet call() throws Exception {
                        return someMethodThatReturnsResultSet(dbTable);
                    }
                }) ;
    
                for(int v=0; v < vehiclesList.size(); v++){
                    final Future<ResultSet> vehicleRs = rsCompletionService.take();
                    completionServiceForAnotherCalc.submit(new Callable<Object>() {
                        @Override
                        public Object call() throws Exception {
                            return anotherMethodToDoSomeComputation((ResultSet) vehicleRs);
                        }
                    });
                }
    
                for(int v=0; v < vehiclesList.size(); v++){
                    Future<Object> finalData = completionServiceForAnotherCalc.take();
                    theList.add(finalData.get());    
                }
            }
    
            return theList;
        }
    
        private static Object anotherMethodToDoSomeComputation(ResultSet rs) {
            return null; 
        }
    
        private static ResultSet someMethodThatReturnsResultSet(String dbTable) {
            return null; 
        }
    }
    

Hope this helps. Let me know in case you need any further clarification.

Upvotes: 1

Bhaskar
Bhaskar

Reputation: 7523

Have a CountDownLatch allResultsDone = new CountDownLatch(list.size())

In your for loop , create Runnable tasks representing one table processing for each task and submit them on some ExecutorService. In each Runnable ,do allResultsDone.countDown() to indicate that this task is completed.

In the method that is responsible for collecting all results and returning to client , do a allResultsDone.await() , just before you return. When all tasks complete , this latch will trip automatically and the method thread can return.

Upvotes: 1

Related Questions