Hi everyone.
Welcome back.
In this lecture, let's explore one of the most powerful and performance oriented gatherers which is
the map concurrent.
This gatherer allows us to process elements in parallel, brings true concurrency to stream transformations
without having to manage threads manually.
Normally when we work with standard stream operations like map or filter, they are processing elements
sequentially one after the other.
But there are many use cases where we want to handle elements concurrently, especially where each element
involves a time consuming operation such as a network call, file processing, or a database query map
concurrent enables exactly that.
It transforms elements concurrently, meaning multiple elements are processed at the same time by different
threads.
The most interesting part of map concurrent is that it gives you the fine grained control over the level
of concurrency.
So in this case, here is an example of a map.
Concurrent you specify the total number of threads you want.
So in this case, number two is what we have given.
That means there is going to be two threads.
That's going to be processing the logic which is processed movie.
In this case the processed movie is a function.
So this function could be a network call.
It could be adding a delay or file based operation.
If you have a use case like that then these kind of operators like map concurrent can come in handy.
Let's explore the syntax step by step.
So the first one here is a concurrency defines how many tasks can run in parallel.
This means you define how many threads that's going to be spun up.
And then which is going to take care of executing the function.
So in this case, if you have to relate what a function is, here is this lambda gatherer is the one
which is going to handle all the concurrency under the hood.
It manages threads, synchronization and ordering automatically for you so you can purely focus on your
transformation logic.
It ensures that there is a safe and efficient parallel processing without needing to use a Completablefuture
Executer service manually.
With this, let's go ahead and explore this code in action.
So I'm back in IntelliJ here.
So in here let's go back to the top.
So first thing is I'm going to show you what the logic is by executing this function which is the demonstrate
sequential map.
So if you go to this function so we have the movies.
And what we are doing over here is that we are actually sleeping the code logic for 100 milliseconds.
Okay.
Once we have that then we are actually creating a map.
And then we are basically having the key as the title and the value as processed movie genre, duration
and everything that's getting added as a value.
Okay, let's execute this code and then see how long it's going to take.
So I have this code to track the start time.
I have the code to track the end time.
We will see what is the total time it took to complete this function.
Let's go to the top and then execute this function.
So there you go.
The sequential map, which is a traditional processing.
It took like one, two, three, five milliseconds.
That's approximately a little over one second.
So that's the time it took because we are sleeping for how many seconds.
We are sleeping for 100 milliseconds.
Now what we are going to do, we are going to be writing the same logic using the demonstrate map concurrent.
Okay.
We have the code for the start time and end time already here.
We don't have to worry about tracking that.
So the next logic is how are we going to write the map concurrent.
So let's start with that code.
So map sorry movies dot stream and then gatherer.
So it's going to be gatherers dot map concurrent.
And I'm going to be providing the value as two.
That means two concurrent threads.
And the rest of the logic is going to be pretty much the same.
Right.
So what we can do is we can actually take the logic from the sequential map that we have.
So whatever that starts from here and ends here, let's copy that logic.
Let's go to the top.
So that's going to be the second argument.
Okay.
So there you go.
We have the second argument here.
Let's put this over here.
There we go.
And after that I'm going to be doing the for each.
This logic is going to be very much similar to what we have down below.
So I'm going to be copying this value and then put it over here.
Okay.
So it's the same logic.
The only difference is that I'm using a gatherer here and I'm passing the map concurrent.
Okay.
Let's run this program one more time.
So overall the sequential processing time it took was one, two, three, five milliseconds.
Let's see, what is the logger we have?
We have the concurrent processing time over here.
Let's run this program one more time.
So here you can see that the concurrent processing time it took six 34 milliseconds.
This is half the time of whatever sequential.
The reason being we have used the concurrency value as number two.
Right.
Which is two.
Let's say I'm going to bump it up to four concurrent threads.
Okay.
Let's run this.
You will see a performance which is much more quicker than the previous one.
So in this case it took like three 28 milliseconds, which is really four times the performance improvement.
Just by changing that value, let's say we have 12 movies.
I know that let's say if I change the value to 12, what happens?
So if I change it to 12 on average it took like 100 milliseconds, actually a little over 100 milliseconds.
This is like ten times better than the performance of sequential processing.
So this is the beauty of map concurrent where you have the ability to manage the threads, which will
give you the performance out of the box for you.
And let's say if you have, uh, more questions on this, on how to understand this, highlight this
code and ask it, actually explain how this works under the hood.
It will give you a quick explanation of how this overall gatherer dot map concurrent works under the
hood.
So you can see right.
Create a word.
This creates a virtual thread executor with the maximum concurrency of 12 threads.
It uses a Java structured concurrency model introduced in the recent versions.
Virtual threads are lightweight and managed by the JVM, not OS threads.
This is execution flow.
Each stream element is submitted to a thread pool as a separate task.
Up to 12 elements are processed simultaneously by different virtual threads.
Results are collected and reordered to match the original stream order.
That's really key actually, so you are not losing the original order.
Back pressure.
If all the threads are busy.
New task wait until a thread becomes available.
So you get all of this by just using the map concurrent, and pass the concurrency and your whole function
logic over here using Lambda.
So this is the beauty of map concurrent.
This is an exciting feature for me because I'm always interested in improving the performance of the
code.
This is a very handy feature when it comes to improving the performance in your stream logic.
I hope you have a pretty good idea about how map concurrent works.
This marks the end of this lecture.
Thank you for watching.