Marcos
Marcos

Reputation: 756

classic for loop vs Arraylist forEach in concurrently filled list - Java 8

I'm using the Java 8 iterator over an array list. Suppose I have an ArrayList of futures, is there any problem/recommendation between using the classic for loop vs the arraylist foreach? (Besides not being able to throw an exception from within the lambda expression)

Suppose that the futureList is being filled concurrently, what would be the best approach? Is the last foreach example prone to any issues?

If I'm not wrong, this is the Arraylist foreach source code from jdk 1.8_131:

@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

These are the two alternatives on which I want advice:

public void waitForCompletion(List<Future<HttpResponse>> futureList) {
    futureList.forEach(future -> {
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.error("Something went wrong: ", e);
        }
    });
}

vs

private void waitForCompletion(List<Future<HttpResponse>> futureList) {
    for(int i=0; i < futureList.size(); i++) {
        try {
            futureList.get(i).get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.error("Something went wrong: ", e);
        }
    }
}

In which case it is not recommended to use?:

private void waitForCompletion(List<Future<HttpResponse>> futureList) {
    for(Future<HttpReponse> future:futureList) {
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.error("Something went wrong: ", e);
        }
    }
}

Upvotes: 1

Views: 1640

Answers (4)

holi-java
holi-java

Reputation: 30696

In your case, you can just using for-each loop instead:

private void waitForCompletion(List<Future<HttpResponse>> futureList) {
    for(Future<HttpReponse> future:futureList) {
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.error("Something went wrong: ", e);
        }
    }
}

Note: ArrayList#get don't support fail-fast feature, so your second approach is different with your first one.

fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

for example:

List<Integer> list = new ArrayList<Integer>();
list.add(1);
Iterator<Integer> it = list.iterator();

it.next(); // return 1;

list.add(2);// the list is modified 
it.next();
// ^--- fail-fast throws a ConcurrentModificationException

Edit

the for-each loop is equivalent to ArrayList#forEach, they are both support fail-fast feature. the different between them is for-each loop can throws a checked Exception directly, for example:

private void waitForCompletion(List<Future<HttpResponse>> futureList)
                                                   throws Exception {
    for(Future<HttpReponse> future:futureList) {
         future.get();
    }
}

Another bit different is Array#forEach only create a lambda expression instance once. but for-each loop will create a new Iterator for each call, for example:

void forEachLoop(){
  //          v--- create a new Iterator when forEachLoop was called.
  for(T item:items);
}

void forEach(){
                // v--- create a single lambda instance.
  items.forEach(it->{});
}

Upvotes: 3

MartinByers
MartinByers

Reputation: 1240

A few years ago I compiled a class containing a for each loop them decomplied it. Using jd-gui.

My loop

for (Object object: objectList) {
    // Stuff
}

Decomplied to

for (into i =0 ; i < objectList.lenght(); i ++) {
    Object object = objectList.get(i);
    // Stuff
}

So they are basically equivalents (or at least they were in java 5)

Upvotes: 0

Flying Dutchman
Flying Dutchman

Reputation: 23

You need to use a synchronized list for your purpose:

List<Future<HttpResponse>> objList = 
      Collections.synchronizedList(new ArrayList<Future<HttpResponse>>());

When performing concurrent thread operations, the access to each independent process must be thread-safe. You are building a thread-execution service, and you will not run the risk that two future-tasks update the list simultaneously, e.g. through their return objects.

Alternative

Note further that you could also configure a threadpool of 'Runnable' Java classes, as elements of your list. Here is an implementation that I have actual production experience with (based on ConcurrentHashMap):

Map<ReturnData, Map<String, String>> returnMap = new ConcurrentHashMap<>();
ExecutorCompletionService<Map<ReturnData, Map<String, String>>> exService = new ExecutorCompletionService<>(executorServiceGetReturnData);

List<Callable<Map<ReturnData, Map<String, String>>>> callMethods = new ArrayList<>(ReturnData.size());

Upvotes: 0

Easton Bornemeier
Easton Bornemeier

Reputation: 1936

Cost wise I believe they are almost identical--just depends what is going on behind the scenes and what you chose to write yourself. Readability-wise, it is usually nicer to use the for-each style loops due to the easy reference of individual objects in a list.

Upvotes: 0

Related Questions