Reputation: 1583
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
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
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:
WHERE
clauseNote 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
Reputation: 616
You have 2 good options:
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
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
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