Java: multithreading matrix & route requests

Kia ora!

Apologies, here’s another question about my Java source implementation, this time re: parallelization for Matrix and Route requests. Note that I do not want to parallelise the internal aspects of the request algorithms themselves, but simply to run several of them simultaneously.

Each process on each thread calls ORS methods from a process-unique ORSGraph object, which I believe is, in itself, threadsafe:

public class ORSGraph extends Graph {
    private APIEnums.Profile car;

    private MatrixRequest req;
    private MatrixResult res;

    public ORSGraph(String name){
        super(name);
        car = APIEnums.Profile.DRIVING_CAR;
    }

    @Override
    public Pair<Double, List<Location>> getShortestPath(Location s, List<Location> tL, boolean needPath) {
        PriorityQueue<Pair<Double,List<Location>>> queue = new PriorityQueue<>(Comparator.comparingDouble(Pair::getFirst));

        for(Location t: tL) {
            Pair<Double, List<Location>> res = getShortestPath(s, t, needPath);
            queue.add(res);
        }

        return queue.poll();
    }

    @Override
    public Pair<Double, List<Location>> getShortestPath(Location i, Location j, boolean needPath) {
        CoordinateLoc from = (CoordinateLoc) i;
        CoordinateLoc to = (CoordinateLoc) j;

        try {
            if(!needPath)
                try {
                    Pair<Double, List<Location>> res = new Pair(Utility.truncate(getDurations(Utility.coordinateLocsToArray(new CoordinateLoc[]{from, to}), new String[]{"0"}, new String[]{"1"})[0]), null);
                    return res;
                } catch (Exception e) {
                    // do nothing. Continue to find the route.
                }

            RouteRequest request1 = new RouteRequest(Utility.coordinateLocsToArray(new CoordinateLoc[]{from, to}));
            request1.setProfile(car);
            request1.setAttributes(new APIEnums.Attributes[]{APIEnums.Attributes.DETOUR_FACTOR});
            request1.setExtraInfo(new APIEnums.ExtraInfo[]{APIEnums.ExtraInfo.OSM_ID});
            request1.setIncludeGeometry(true);
            request1.setRoutePreference(APIEnums.RoutePreference.FASTEST);
            RouteRequestOptions options = new RouteRequestOptions();
            options.setAvoidBorders(APIEnums.AvoidBorders.CONTROLLED);
            request1.setRouteOptions(options);

            RouteResult[] routeRes = request1.generateRouteFromRequest();
            if(routeRes != null && routeRes.length > 0) {
                for (RouteResult rr : routeRes) {
                    List<Location> res = new LinkedList<>();
                    int len = rr.getGeometry().length;
                    Coordinate[] coords = rr.getGeometry();
                    for(int k = 0; k < len; k++){
                        Coordinate c = coords[k];
                        res.add(new CoordinateLoc(c.y, c.x));
                    }
                    Pair<Double, List<Location>> resPair = new Pair<>(Utility.truncate(rr.getSummary().getDuration()/60), res);
                    return resPair;
                }
            }
        } catch (Exception e){
            throw new IllegalStateException("Your route request failed: from " + from.toString() + " to " + to.toString() + " error: " + e.getMessage());
        }

        return null;
    }

    @Override
    public Location getCurrLoc(DecisionProcessState state, Agent a) {
        List<Location> currPath = a.getCurrPath();

        try {
            if (currPath.size() > 0) {
                List<DecisionProcessEvent> eList = a.getEvents();
                double startTime = 0.0;
                if (eList.size() > 1)
                    startTime = eList.get(eList.size() - 2).getTime();

                double currTime = state.getTime();

                double sumTime = 0.0;
                CoordinateLoc curr = (CoordinateLoc) currPath.get(0);
                for (int i = 1; i < currPath.size(); i++) {
                    CoordinateLoc next = (CoordinateLoc) currPath.get(i);
                    double arcTime = getShortestPath(curr, next, true).getFirst();
                    if ((startTime + sumTime + arcTime) > currTime) {
                        double diff = startTime + sumTime + arcTime - currTime;
                        double arcFrac = Utility.round(diff / arcTime);

                        if (!(arcFrac <= 1.0 && arcFrac >= 0.0)) {
                            if (currPath.size() > i) {
                                curr = next;
                                continue;
                            } else
                                throw new IllegalStateException("Your arc fraction must be between 0.0 and 1.0. Arc frac: " + arcFrac + " diff: " + diff + " startTime: " + startTime + " sumTime: " + sumTime + " arcTime: " + arcTime + " currTime: " + currTime + " path size: " + currPath.size() + " i: " + i);
                        }
                        if (arcFrac + (1 - arcFrac) != 1)
                            throw new IllegalStateException("Your arc fraction must be [0,1]. Something is going wrong. Arc frac: " + arcFrac + " diff: " + diff + " startTime: " + startTime + " sumTime: " + sumTime + " arcTime: " + arcTime + " currTime: " + currTime + " path size: " + currPath.size() + " i: " + i);

                        double newX = Double.min(curr.getX(), next.getX()) + Math.abs(curr.getX() - next.getX()) * arcFrac;
                        double newY = Double.min(curr.getY(), next.getY()) + Math.abs(curr.getY() - next.getY()) * arcFrac;

                        return new CoordinateLoc(newX, newY);
                    } else {
                        sumTime += arcTime;
                        curr = next;
                    }
                }
            }
        } catch (Exception e){
            e.printStackTrace();
            throw new IllegalStateException("You have failed to find an agent's current location: " + e.getMessage());
        }

        return a.getCurrLoc();
    }

    public double[] getDurations(Double[][] coordinates, String[] sources, String[] destinations) throws StatusCodeException {
        req = new MatrixRequest(coordinates);
        req.setSources(sources);
        req.setDestinations(destinations);
        req.setProfile(car);
        req.setMetrics(new MatrixRequestEnums.Metrics[]{MatrixRequestEnums.Metrics.DURATION});
        req.setResponseType(APIEnums.MatrixResponseType.JSON);

        res = req.generateMatrixFromRequest();
        float[] durations = res.getTable(1);
        double[] durSec = new double[durations.length];
        for(int i = 0; i < durations.length; i++) // convert
            durSec[i] = durations[i] / 60;
        return durSec;
    }

    public synchronized Graph clone(){
        return new ORSGraph(this.getGlobal_name());
    }
}

When running my code on a single thread, I get repeatable results. However, if I run a multithreaded process, such results are not repeatable.

I attempted to tie a RoutingProfileManager object to each Graph, however, this very quickly lead to a memory issue (while also being rather ‘hacky’).

Is this (currently) infeasible, or am I making a flawed assumption?

Cheers,
Mak