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 Stream
s), 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.