using Threading in for loop of ArrayList from a method

Created some snippets where you can try dropping your actual processing code into.

My test data looks like this:

try (PrintWriter pw = new PrintWriter("testdata.txt")) {
    for (int i = 0; i < 1000000; i++)
        pw.println(i);
}

So a textfile with a million numbers in its lines.
My “task” was to create a file containing double the value of the same lines, disregarding their order:

pw.println(Integer.parseInt(line) * 2);

where line is a line from the input file, and pw is a PrintWriter for the output.
In actual code:

try (PrintWriter pw = new PrintWriter("testresult.txt");
        BufferedReader br = new BufferedReader(new FileReader("testdata.txt"))) {
    String line;
    while ((line = br.readLine()) != null)
        pw.println(Integer.parseInt(line) * 2);
}

This is something which can be written shorter, and perhaps a bit more readable with streaming:

try (PrintWriter pw = new PrintWriter("testresult.txt")) {
    Files.lines(Paths.get("testdata.txt")).forEach(
        line -> pw.println(Integer.parseInt(line) * 2));
}

The two snippets produce very similar execution times, around 1.6-1.7 seconds on my machine (measured with the “budget” approach, long start = System.currentTimeMillis(); before and System.out.println(System.currentTimeMillis() - start); after).

Then the stream can be parallelized with a single .parallel() inside:

try (PrintWriter pw = new PrintWriter("testresult.txt")) {
    Files.lines(Paths.get("testdata.txt")).parallel().forEach(
        line -> pw.println(Integer.parseInt(line) * 2));
}

This will produce mixed order results.
A side remark on println(int): it’s not documented, but its actual implementation is thread-safe, however if you want to be absolutely “safe” and build on the documented features only, you should synchronize it yourself:

try (PrintWriter pw = new PrintWriter("testresult.txt")) {
    Files.lines(Paths.get("testdata.txt")).parallel().forEach(line -> {
        synchronized (pw) {
            pw.println(Integer.parseInt(line) * 2);
        }
    });
}

Both of them are actually slower than the sequential (2 and 2.2 seconds, the extra manual synchronization does matter), but of course it matters a lot that this processing step is very simple. So it’s important to keep in mind that if file operations eat up the time in your case too, parallelism can not really help that.

And for comparison, a complete snippet using a thread pool:

ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ExecutorCompletionService<String> ecs = new ExecutorCompletionService<String>(es);
int counter=0;
try (BufferedReader br = new BufferedReader(new FileReader("testdata.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        final String current = line;
        ecs.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return Integer.toString(Integer.parseInt(current)*2);
            }
        });
        counter++;
    }
}
try (PrintWriter pw = new PrintWriter("testresult.txt")) {
    while(counter>0) {
        pw.println(ecs.take().get());
        counter--;
    }
}
es.shutdown();

This one is the longest of them for sure, on the other hand it runs for 2 seconds, so comparable to the synchronized-less streaming sample, and it’s “safe” without it as file operations all happen in the main thread (workers only calculate and stringify). Going for full-manual threads could make things even more verbose, but I don’t feel motivated to write such code at the moment.

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top