Home > Back-end >  How to synchonized this lambda statement?
How to synchonized this lambda statement?

Time:01-19

Java / JDK 19. How to synchonized this lambda statement?

package sybex.ch00.exercies;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class Q03 {

    public static void main(String[] args) {
        List<Integer> data = new ArrayList<>();
        IntStream.range(0, 100).parallel().forEach(s -> data.add(s));
        System.out.println(data.size());
    }
}

I read book, they said after synchronized lamba will make thread safe, and return 100, but I don't know how to do. Please guide me.

CodePudding user response:

From a Stream point of view, external synchronization is the wrong way to do it (forced by a bad use of side effects). The docs state this about side effects:

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards.

Also:

Many computations where one might be tempted to use side effects can be more safely and efficiently expressed without side-effects, such as using reduction instead of mutable accumulators. However, side-effects such as using println() for debugging purposes are usually harmless. A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.

Additionally external synchronization will hurt performance:

If executed in parallel, the non-thread-safety of ArrayList would cause incorrect results, and adding needed synchronization would cause contention, undermining the benefit of parallelism. Furthermore, using side-effects here is completely unnecessary; the forEach() can simply be replaced with a reduction operation that is safer, more efficient, and more amenable to parallelization

The correct way to do it (for Streams), would be to use a Collector.

List<Integer> data = IntStream.range(0, 100)
        .parallel()
        .boxed()
        .collect(Collectors.toList());
System.out.println(data.size());

CodePudding user response:

No special trick to it, just use a synchronized block:

public class Q03 {

    public static void main(String[] args) {
        List<Integer> data = new ArrayList<>();
        IntStream.range(0, 100).parallel().forEach(s -> {
            synchronized(data) { data.add(s); }
        });
        System.out.println(data.size());
    }
}

Depending on the context you are running this in you will have to choose what object to synchronize on. Here data is a good choice, or you could create an object to lock on:

public class Q03 {

    public static void main(String[] args) {
        List<Integer> data = new ArrayList<>();
        Object lock = new Object();
        IntStream.range(0, 100).parallel().forEach(s -> {
            synchronized(lock) { data.add(s); }
        });
        System.out.println(data.size());
    }
}

CodePudding user response:

I would use Collections.synchronizedList:

public static void main(String[] args) {
    List<Integer> data = Collections.synchronizedList(new ArrayList<>());
    IntStream.range(0, 100).parallel().forEach(s -> data.add(s));
    System.out.println(data.size());
}

Just take care when you use the iterator of that list, see the linked JavaDocs.

Alternatively you can use one of the concurrent collections in java.util.concurrent, but the CopyOnWriteArrayList there could have significant performance issues for the case you showed. On the other hand I didn't check performance for the solution above.

  •  Tags:  
  • java
  • Related