开发者

Concurrency and Calendar classes

I have a thread (class implementing runnable, called AnalyzeTree) organised around a hash map (ConcurrentMap> slicesMap). The class goes through the data (called trees here) in the large text file and parses the geographical coordinates from it to the HashMap. The idea is to process one tree at a time and add or grow the values according to the key (which is just a Double value representing time).

The relevant part of code looks like this:

// grow map entry if key exists
if (slicesMap.containsKey(sliceTime)) {

    double[] imputedLocation = imputeValue(
        location, parentLocation, sliceHeight,
        nodeHeight, parentHeight, rate,
        useTrueNoise, currentTreeNormalization,
        precisionArray);

    slicesMap.get(sliceTime).add(new Coordinates(imputedLocation[1], imputedLocation[0], 0.0));

    // start new entry if no such key in the map
} else {

    List<Coordinates> coords = new ArrayList<Coordinates>();

    double[] imputedLocation = imputeValue(
        location, parentLocation, sliceHeight,
        nodeHeight, parentHeight, rate,
        useTrueNoise, currentTreeNormalization,
        precisionArray);

    coords.add(new Coordinates(imputedLocation[1], imputedLocation[0], 0.0));

    slicesMap.putIfAbsent(sliceTime, coords);
    // slicesMap.put(sliceTime, coords);

}// END: key check

And the class is called like this (executor is ExecutorService executor = Executors.newFixedThreadPool(NTHREDS) ):

mrsd = new SpreadDate(mrsdString);
int readTrees = 1;
while (treesImporter.hasTree()) {

    currentTree = (RootedTree) treesImporter.importNextTree();

    executor.submit(new AnalyzeTree(currentTree,
        precisionString, coordinatesName, rateString,
        numberOfIntervals, treeRootHeight, timescaler,
        mrsd, slicesMap, useTrueNoise));

    // new AnalyzeTree(currentTree, precisionString,
    // coordinatesName, rateString, numberOfIntervals,
    // treeRootHeight, timescaler, mrsd, slicesMap,
    // useTrueNoise).run();

    readTrees++;

}// END: while has trees

Now this is running into troubles when executed in parallel (the commented part running sequentially is fine), I thought it might throw a ConcurrentModificationException, but apparently the problem is in mrsd (instance of SpreadDate object, which is simply a class for date related calculations).

The SpreadDate class looks like this:

public class SpreadDate {

    private Calendar cal;
    private SimpleDateFormat formatter;
    private Date stringdate;

    public SpreadDate(String date) throws ParseException {

        // if no era specified assume current era
        String line[] = date.split(" ");
        if (line.length == 1) {
            StringBuilder properDateStringBuilder = new StringBuilder();
            date = properDateStringBuilder.append(date).append(" AD").toString();
        }

        formatter = new SimpleDateFormat("yyyy-MM-dd G", Locale.US);
        stringdate = formatter.parse(date);

        cal = Calendar.getInstance();
    }

    public long plus(int days) {
        cal.setTime(stringdate);
        cal.add(Calendar.DATE, days);
        return cal.getTimeInMillis();
    }// END: plus

    public long minus(int days) {
        cal.setTime(stringdate);
        cal.add(Calendar.DATE, -days); //line 39
        return cal.getTimeInMillis();
    }// END: minus

    public long getTime() {
        cal.setTime(stringdate);
        return cal.getTimeInMillis();
    }// END: getDate
}

And the stack trace from when exception is thrown:

java.lang.ArrayIndexOutOfBoundsException: 58
at     sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:454)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2098)
at java.util.G开发者_StackOverflowregorianCalendar.computeFields(GregorianCalendar.java:2013)
at java.util.Calendar.setTimeInMillis(Calendar.java:1126)
at java.util.GregorianCalendar.add(GregorianCalendar.java:1020)
at utils.SpreadDate.minus(SpreadDate.java:39)
at templates.AnalyzeTree.run(AnalyzeTree.java:88)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:636)

If a move the part initializing mrsd to the AnalyzeTree class it runs without any problems - however it is not very memory efficient to initialize class each time this thread is running, hence my concerns. How can it be remedied?


Calendar and Date are two example of mutable classes. You seem to be sharing them across the ExecutorService and the results as you see are unexpected. I would suggest creating a new instance of the SpreadDate object for each thread.


As mentioned in the previous answers, classes like Calendar and SimpleDateFormat are not thread-safe, so you cannot access them concurrently from multiple threads. (JavaDocs often specify explicitly which classes are thread-safe and which aren't.)

One option is to create different instances for different threads (in your case, different instances of SpreadDate).

Another option is to use Java's ThreadLocal mechanism. It allows creating an instance per thread - if the same thread performs several tasks, it will use the same instance over and over again. This can provide a nice balance - your code is thread-safe, but you're not allocating massive amounts of objects and not waiting on synchronization.

As with any optimization, I suggest considering carefully if you actually need it - judging by the code above, I'm not sure you have much to gain. Should you choose to use it, it would look something like:

public class SpreadDate {

    private static ThreadLocal<Calendar> calThreadLocal;
    private SimpleDateFormat formatter;
    private Date stringdate;

    public SpreadDate(String date) throws ParseException {
        // ...skipped...

        calThreadLocal = new ThreadLocal<Calendar>() {

            @Override
            protected Calendar initialValue() {
                return Calendar.getInstance();
            }
        };
    }

    public long plus(int days) {
        Calendar cal = calThreadLocal.get();
        cal.setTime(stringdate);
        cal.add(Calendar.DATE, days);
        return cal.getTimeInMillis();
    }// END: plus

    // ...skipped...
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜