while trying to get familiar with Flux I want to have a stream of Data within a Flux.
the .map() function should simulate a workload which requires some random processing time per Magician object and is happening in separate Threads (as you can see in the output at Thread id to make use of Multi-Threading.
Finally the stream should return the results in order. But no matter what I do, e.g. using .sequential(), or where it always ends up in in being unordered.
Can someone tell me how I can make use of Multi-Threading (using .map() or any other sequence as long as the result is the one asked) and having the results in order please? Thank you
P.S. The business logic behind this question is that I am calling an Endpoint that delivers a Flux<byte[]>
These bytes I need to process in parallel (decryption) and therefor forward in order to another consumer.
public class FluxRunner {
public void getWorkerStream() throws InterruptedException {
final int[] counter = new int[1];
// create workers
final Flux<Magician[]> workersFlux = Flux.range(0, 10)
.map(integer -> {
final Magician[] worker = new Magician[1];
worker[0] = new Magician(counter[0], counter[0]++);
return worker;
});
final Disposable disposable = workersFlux
.parallel()
.runOn(Schedulers.parallel())
.map(workers -> {
System.out.println("Thread id: " + Thread.currentThread().getId());
workers[0].calculate();
return workers;
})
.sequential() // no effect, doOnNext is unordered
.doOnNext(workers -> System.out.println(workers[0].getId()))
.subscribe();
while (!disposable.isDisposed()) {
sleep(500);
}
}
}
#Data
class Magician {
final int id;
int number;
Magician(final int id, final int number) {
this.id = id;
this.number = number;
}
public void calculate() {
int timeToSleep = (int) (Math.random() * 3000);
System.out.println("Sleep for " + timeToSleep + " seconds");
try {
sleep(timeToSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
number++;
}
}
result will be
Thread id: 33
Thread id: 24
Thread id: 25
Thread id: 31
Thread id: 29
Thread id: 30
Thread id: 27
Thread id: 32
Thread id: 28
Thread id: 26
Sleep for 2861 seconds
Sleep for 2811 seconds
Sleep for 711 seconds
Sleep for 2462 seconds
Sleep for 1858 seconds
Sleep for 601 seconds
Sleep for 126 seconds
Sleep for 359 seconds
Sleep for 2014 seconds
Sleep for 2356 seconds
4
5
7
9
8
3
0
1
6
2
You can use flatMapSequential operator along with .subscribeOn(Schedulers.parallel()) to get the desired result:
final Disposable disposable = workersFlux
.flatMapSequential(workers -> Mono.fromCallable(() -> {
System.out.println("Thread id: " + Thread.currentThread().getId());
workers[0].calculate();
return workers;
}).subscribeOn(Schedulers.parallel()))
.doOnNext(workers -> System.out.println(workers[0].getId()))
.subscribe();
Related
I'm trying locate the first (any) member of a list that matches a given predicate like so:
Item item = items.parallelStream()
.map(i -> i.doSomethingExpensive())
.filter(predicate)
.findAny()
.orElse(null);
I would expect that once findAny() gets a match, it would return immediately, but that doesn't appear to be the case. Instead it seems to wait for the map method to finish on most of the elements before returning. How can I return the first result immediately and cancel the other parallel streams? Is there a better way to do this than using streams such as CompletableFuture?
Here's a simple example to show the behavior:
private static void log(String msg) {
private static void log(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " " + msg);
}
Random random = new Random();
List<Integer> nums = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
Optional<Integer> num = nums.parallelStream()
.map(n -> {
long delay = Math.abs(random.nextLong()) % 10000;
log("Waiting on " + n + " for " + delay + " ms");
try { Thread.sleep(delay); }
catch (InterruptedException e) { System.err.println("Interruption error"); }
return n * n;
})
.filter(n -> n < 30)
.peek(n -> log("Found match: " + n))
.findAny();
log("First match: " + num);
Log output:
14:52:27.061 Waiting on 9 for 2271 ms
14:52:27.061 Waiting on 2 for 1124 ms
14:52:27.061 Waiting on 13 for 547 ms
14:52:27.061 Waiting on 4 for 517 ms
14:52:27.061 Waiting on 1 for 1210 ms
14:52:27.061 Waiting on 6 for 2646 ms
14:52:27.061 Waiting on 0 for 4393 ms
14:52:27.061 Waiting on 12 for 5520 ms
14:52:27.581 Found match: 16
14:52:27.582 Waiting on 3 for 5365 ms
14:52:28.188 Found match: 4
14:52:28.275 Found match: 1
14:52:31.457 Found match: 0
14:52:32.950 Found match: 9
14:52:32.951 First match: Optional[0]
Once a match is found (in this case 16), findAny() does not return immediately, but instead blocks until the remaining threads finish. In this case, the caller is waiting an extra 5 seconds before returning after a match has already been found.
Instead it seems to wait for the map method to finish on most of the elements before returning.
This is not correct.
When speaking of the elements which are already being processed, it will wait for the completion of all of them, as the Stream API allows concurrent processing of data structures which are not intrinsically thread safe. It must ensure that all potential concurrent access has been finished before returning from the terminal operation.
When talking about the entire stream, it’s simply not fair to test a stream of only 14 elements on an 8 core machine. Of course, there will be at least 8 concurrent operations started, that’s what it is all about. You are adding fuel to the flames by using findFirst() instead of findAny(), as that doesn’t mean returning the first found element in processing order, but the first element in encounter order, i.e. exactly zero in your example, so threads processing other chunks than the first can’t assume that their result is the correct answer and are even more willing to help processing other candidates than with findAny().
When you use
List<Integer> nums = IntStream.range(0, 200).boxed().collect(Collectors.toList());
Optional<Integer> num = nums.parallelStream()
.map(n -> {
long delay = ThreadLocalRandom.current().nextInt(10_000);
log("Waiting on " + n + " for " + delay + " ms");
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(delay));
return n * n;
})
.filter(n -> n < 40_000)
.peek(n -> log("Found match: " + n))
.findAny();
log("First match: " + num);
You will get a similar number of tasks running to completion, despite the far bigger number of stream elements.
Note that CompletableFuture also doesn’t support interruption, so the only builtin feature for returning any result and canceling the other jobs, that comes into my mind, is the old ExecutorService.invokeAny.
To built the feature of mapping and filtering for it, we can use the following helper function:
static <T,R> Callable<R> mapAndfilter(T t, Function<T,R> f, Predicate<? super R> p) {
return () -> {
R r = f.apply(t);
if(!p.test(r)) throw new NoSuchElementException();
return r;
};
}
Unfortunately, there’s only the option of completing with a value or exceptionally, therefore we have to use an exception for non-matching elements.
Then we can use it like
ExecutorService es = ForkJoinPool.commonPool();
Integer result = es.invokeAny(IntStream.range(0, 100)
.mapToObj(i -> mapAndfilter(i,
n -> {
long delay = ThreadLocalRandom.current().nextInt(10_000);
log("Waiting on " + n + " for " + delay + " ms");
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(delay));
return n * n;
},
n -> n < 10_000))
.collect(Collectors.toList()));
log("result: "+result);
and it will not only cancel the pending tasks, it will return without waiting for them to finish.
Of course, this implies that the source data, the jobs operating upon, must be either immutable or thread safe.
You can use this code to illustrate how parallelStream works:
final List<String> list = Arrays.asList("first", "second", "third", "4th", "5th", "7th", "8th", "9th", "10th", "11th", "12th", "13th");
String result = list.parallelStream()
.map(s -> {
System.out.println("map: " + s);
return s;
})
.filter(s -> {
System.out.println("fiter: " + s);
return s.equals("8th");
})
.findFirst()
.orElse(null);
System.out.println("result=" + result);
There are two options to achieve what you're looking for, to stop expensive operation with a filter:
Don't use streams at all, use a simple for or enhanced for
Filter first, then map with the expensive operation
There are several things at play here. The first thing is that parallelStream() uses the common ForkJoinPool by default, which makes the calling thread participate as well. This means that if one of the slow tasks is currently running on the calling thread, it has to finish before the caller gets the control back.
You can see this by modifying the code a little bit to log the thread names, and log when finished the wating:
private static void log(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " [" + Thread.currentThread().getName() + "] " + " " + msg);
}
public static void main(String[] args) {
Random random = new Random();
List<Integer> nums = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
Optional<Integer> num = nums.parallelStream()
.map(n -> {
long delay = Math.abs(random.nextLong()) % 10000;
log("Waiting on " + n + " for " + delay + " ms");
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.err.println("Interruption error");
}
log("finished waiting");
return n * n;
})
.filter(n -> n < 30)
.peek(n -> log("Found match: " + n))
.findAny();
log("First match: " + num);
}
Sample output:
13:56:52.954 [main] Waiting on 9 for 9936 ms
13:56:52.956 [ForkJoinPool.commonPool-worker-1] Waiting on 4 for 7436 ms
13:56:52.970 [ForkJoinPool.commonPool-worker-2] Waiting on 1 for 6523 ms
13:56:52.983 [ForkJoinPool.commonPool-worker-3] Waiting on 6 for 7488 ms
13:56:59.494 [ForkJoinPool.commonPool-worker-2] finished waiting
13:56:59.496 [ForkJoinPool.commonPool-worker-2] Found match: 1
13:57:00.392 [ForkJoinPool.commonPool-worker-1] finished waiting
13:57:00.392 [ForkJoinPool.commonPool-worker-1] Found match: 16
13:57:00.471 [ForkJoinPool.commonPool-worker-3] finished waiting
13:57:02.892 [main] finished waiting
13:57:02.894 [main] First match: Optional[1]
Here as you can see, 2 matches are found but the main thread is still busy, so it cannot return the match now.
This does not always explain all cases though:
13:58:52.116 [main] Waiting on 9 for 5256 ms
13:58:52.143 [ForkJoinPool.commonPool-worker-1] Waiting on 4 for 4220 ms
13:58:52.148 [ForkJoinPool.commonPool-worker-2] Waiting on 1 for 2136 ms
13:58:52.158 [ForkJoinPool.commonPool-worker-3] Waiting on 6 for 7262 ms
13:58:54.294 [ForkJoinPool.commonPool-worker-2] finished waiting
13:58:54.295 [ForkJoinPool.commonPool-worker-2] Found match: 1
13:58:56.364 [ForkJoinPool.commonPool-worker-1] finished waiting
13:58:56.364 [ForkJoinPool.commonPool-worker-1] Found match: 16
13:58:57.399 [main] finished waiting
13:58:59.422 [ForkJoinPool.commonPool-worker-3] finished waiting
13:58:59.424 [main] First match: Optional[1]
This might be explained by the way the fork-join pool merges the results. It seems some improvements are possible.
As an alternative, you could indeed do this using CompletableFuture:
// you should probably also pass your own executor to supplyAsync()
List<CompletableFuture<Integer>> futures = nums.stream().map(n -> CompletableFuture.supplyAsync(() -> {
long delay = Math.abs(random.nextLong()) % 10000;
log("Waiting on " + n + " for " + delay + " ms");
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.err.println("Interruption error");
}
log("finished waiting");
return n * n;
})).collect(Collectors.toList());
CompletableFuture<Integer> result = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(unused -> futures.stream().map(CompletableFuture::join).filter(n -> n < 30).findAny().orElse(null));
// shortcircuiting
futures.forEach(f -> f.thenAccept(r -> {
if (r < 30) {
log("Found match: " + r);
result.complete(r);
}
}));
// cancelling remaining tasks
result.whenComplete((r, t) -> futures.forEach(f -> f.cancel(true)));
log("First match: " + result.join());
Output:
14:57:39.815 [ForkJoinPool.commonPool-worker-1] Waiting on 0 for 7964 ms
14:57:39.815 [ForkJoinPool.commonPool-worker-3] Waiting on 2 for 5743 ms
14:57:39.817 [ForkJoinPool.commonPool-worker-2] Waiting on 1 for 9179 ms
14:57:45.562 [ForkJoinPool.commonPool-worker-3] finished waiting
14:57:45.563 [ForkJoinPool.commonPool-worker-3] Found match: 4
14:57:45.564 [ForkJoinPool.commonPool-worker-3] Waiting on 3 for 7320 ms
14:57:45.566 [main] First match: 4
Note that the cancel(true) does not actually cancel the ongoing tasks (no interruption will occur for example), but it prevents further tasks from being run (you can even see that it might not be immediate since worker 3 still started to execute the next one).
You should also use your own executor, with the appropriate size based on whether it is more CPU or I/O intensive. As you can see, the default uses the common pool and thus it does not use all cores.
The allOf() is mainly needed in case no match is found. If you can guarantee that there is at least one match, you could simply use a `new CompletableFuture() instead.
Finally, as a simple approach I repeated the filter check, but it's easy to move that logic inside the main logic, return null or a marker, and then test for that in both places.
See also How to make a future that gets completed when any of the given CompletableFutures is completed with a result that matches a certain predicate?
Talk is cheap. Show the code.
MyCyclicBarrier.java
public class MyCyclicBarrier extends Thread{
private CyclicBarrier cyclicBarrier;
public MyCyclicBarrier(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
#Override
public void run() {
System.out.println("Thread start." + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2); //biz code
System.out.println("Thread "+Thread.currentThread().getName()+" is waiting for the other Threads."+
"\n\t\t\t\tIt's parties is "+cyclicBarrier.getParties()+
"\n\t\t\t\tWaiting for "+cyclicBarrier.getNumberWaiting()+" Threads");
cyclicBarrier.await(3,TimeUnit.SECONDS);
} catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
e.printStackTrace();
}
System.out.println("Thread end."+Thread.currentThread().getName());
}
}
TestCyclicbarrier.java
public class TestCyclicbarrier1 {
public static void main(String[] args) {
int length = 5;
long start = System.currentTimeMillis();
CyclicBarrier cyclicBarrierWithRunnable = new CyclicBarrier(length, () -> {
System.out.println("the final reach Thread is " + Thread.currentThread().getName());
long end = System.currentTimeMillis();
System.out.println("cost totally :" + (end - start) / 1000 + "s");
});
for (int i = 0; i < length; i++) {
if (i != 4) {
new MyCyclicBarrier(cyclicBarrierWithRunnable).start();
} else {
try {
TimeUnit.SECONDS.sleep(2);
new MyCyclicBarrier(cyclicBarrierWithRunnable).start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Output:
Thread start.Thread-1
Thread start.Thread-0
Thread start.Thread-2
Thread start.Thread-3
Thread Thread-0 is waiting for the other Threads.
It's parties is 5
Waiting for 0 Threads
Thread Thread-3 is waiting for the other Threads.
It's parties is 5
Waiting for 0 Threads
Thread start.Thread-4
Thread Thread-1 is waiting for the other Threads.
It's parties is 5
Waiting for 0 Threads
Thread Thread-2 is waiting for the other Threads.
It's parties is 5
Waiting for 1 Threads
Thread Thread-4 is waiting for the other Threads.
It's parties is 5
Waiting for 4 Threads
the final reach Thread is Thread-4
cost totally :4s
Thread end.Thread-4
Thread end.Thread-0
Thread end.Thread-3
Thread end.Thread-2
Thread end.Thread-1
I am searching for a long time on net. But no similar answer. Please help or try to give some ideas! And I just start to learn CyclicBarrier.
I wonder if I have misunderstood CyclicBarrier.await(int timeout,TimeUnit unit). Threads 0 through 3 have already reached the barrier point that cost 2s.In the same time the final Thread started after 2s of waiting.After 1 second number 0 to 3 Threads reach the specified timeout which number 4 thread still excuted its own code. Here is the question: Why did CyclicBarrier.await(int timeout, TimeUnit unit) didn't throw TimeOutException here?
Threads 0 through 3 have already reached the Barrier point that cost 2s.
This is correct.
In the same time the final Thread started after 2s of waiting.
Correct. Note, by the time this thread starts, other 4 threads are awaiting the CB (3 secs timeout i.e., we have 3 secs until a TimeoutException can occur).
But thread 4 sleeps for only 2 seconds in the run method (we still have only 1 sec until the TimeoutException).
When it comes to await, it is the last thread - so it doesn't have to wait anymore. Hence the barrier action gets run and others are unblocked - from javadoc,
If the current thread is the last thread to arrive, and a
non-null barrier action was supplied in the constructor, then the current thread runs the action before allowing the other threads to continue.
If you make sleep for four seconds before starting thread-4, you would get a TimeoutException.
try {
TimeUnit.SECONDS.sleep(4);
new MyCyclicBarrier(cyclicBarrierWithRunnable).start();
} catch (InterruptedException e) {
e.printStackTrace();
}
You seem to think that the timeout starts when the thread starts:
Threads 0 through 3 have already reached the Barrier point that cost 2s.
After 1 second number 0 to 3 Threads reach the specified timeout
This is wrong. When you call
cyclicBarrier.await(3,TimeUnit.SECONDS);
it doesn't matter how long it took the threads to reach that point - the timeout is 3 seconds from the moment the method cyclicBarrier.await() is called.
Since thread 4 has only an additional delay of 2 seconds it still arrives in time.
To clarify further this is what the timeline looks like:
t=0s
main() creates the CyclicBarrier and starts threads 0 to 3
the threads 0 to 3 start and call TimeUnit.SECONDS.sleep(2);
main calls TimeUnit.SECONDS.sleep(2);
t=2s
main() starts thread 4
the threads 0 to 3 awake, print out something and then call cyclicBarrier.await(3,TimeUnit.SECONDS); which means that they will be interrupted at t=5s (t=2s + 3s)
thread 4 stars and calls TimeUnit.SECONDS.sleep(2);
t=4s
thread 4 awakes, prints out something and then calls cyclicBarrier.await(3,TimeUnit.SECONDS);.
since now all threads are within cyclicBarrier.await(3,TimeUnit.SECONDS);, the condition for the CyclicBarrier is fulfilled and all threads continue
the timeout for thread 4 doesn't get used (because it is the last thread to reach the CyclicBarrier)
for threads 0 to 3 the timeout at t=5s is never reached
As per my understanding if there are multiple threads executing in parallel, Thread.sleep() will only sleep the thread in which it is called. However in my case Thread.sleep is making other threads sleep/wait. Is this expected behavior or am i doing something incorrect.
Find below my code
package stackoverflow;
public class SO {
public static void main(String[] args){
CallMe callMe = new CallMe();
Producer producer = new Producer(callMe);
Consumer consumer = new Consumer(callMe);
producer.thread.start();
consumer.thread.start();
}
}
class CallMe{
int a;
synchronized public int get(){
System.out.println("Get "+a);
return a;
}
synchronized public void put(int a){
System.out.println("Put "+a);
this.a=a;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Producer implements Runnable{
Thread thread;
CallMe callMe;
Producer(CallMe callMe){
thread=new Thread(this);
this.callMe=callMe;
}
public void run(){
int i=0;
while(true){
callMe.put(i);
i++;
}
}
}
class Consumer implements Runnable{
Thread thread;
CallMe callMe;
Consumer(CallMe callMe){
thread = new Thread(this);
this.callMe=callMe;
}
public void run(){
while (true) {
callMe.get();
}
}
}
While the put thread is put to sleep, the get should keep printing the output right? I am not giving any priority to any of the threads so if one thread is sleeping other should start executing. Isn't it?
I tried giving large value of sleep (100000000 millis) still console only shows Put 0 not printing Get <X> at all.
You have synchronized on both methods of your CallMe class. There is only one lock on which to synchronize for each object of that class. And you produce a singleton of that class. So one object with one lock being called via either of two methods across two threads.
Within one of these methods you sleep. During that sleep, you are holding the lock. The consumer thread calls the get method while the producer is holding the lock. The get method blocks, waiting for the lock, stopping the thread of the consumer. Eventually the producer thread wakes, releases the lock. The consumer thread grabs the lock, and proceeds.
This behavior is shown on the tutorial by Oracle.
Be sure to study the classic book by Brian Goetz, et al., Java Concurrency in Practice.
You have another issue as well.
You are sharing a resource, the int member field of CallMe, across two threads. Your code there is subject to visibility problems per the Java Memory Model. Because of modern CPU architecture, two threads accessing the same primitive value or object reference may see two different cached values. One solution is use of volatile keyword. I prefer the alternative, using the Atomic… classes.
I would replace that that with a final member field of type AtomicInteger.
Using AtomicInteger would also allow you to eliminate the synchronized tags, thereby eliminating your frozen-thread bottleneck.
So, killing two birds with one stone, we solve both your contention problem and your visibility problem by using AtomicInteger.
I suggest you learn about using executor service rather than addressing the Thread class directly.
To run a task repeatedly, use a scheduled executor service rather than sleeping.
Putting all that advice together, we might get code that looks like this.
package work.basil.example.prodcon;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
public class CountManager
{
final AtomicInteger counter;
// Constructor
public CountManager ( )
{
this.counter = new AtomicInteger();
System.out.println( "CountManager constructor initialized the count to = " + this.counter.get() + " at " + Instant.now() );
}
public int getCount ( )
{
return this.counter.get();
}
public int increment ( )
{
return this.counter.incrementAndGet();
}
}
package work.basil.example.prodcon;
import java.time.Instant;
public class Producer implements Runnable
{
final private CountManager countManager;
public Producer ( CountManager countManager )
{
this.countManager = countManager;
}
#Override
public void run ( )
{
int newCount = this.countManager.increment();
System.out.println( "The producer in thread # " + Thread.currentThread().getId()+ " incremented newCount = " + newCount + " at " + Instant.now() );
}
}
package work.basil.example.prodcon;
import java.time.Instant;
public class Consumer implements Runnable
{
final private CountManager countManager;
public Consumer ( CountManager countManager )
{
this.countManager = countManager;
}
#Override
public void run ( )
{
int currentCount = countManager.getCount();
System.out.println( "The consumer on thread # " + Thread.currentThread().getId() + " reports currentCount = " + currentCount + " at " + Instant.now() );
}
}
Run that code in an app.
package work.basil.example.prodcon;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class App
{
public static void main ( String[] args )
{
App app = new App();
app.demo();
}
private void demo ( )
{
System.out.println( "The app began demo on thread # " + Thread.currentThread().getId() + " at " + Instant.now() );
// Setup.
CountManager countManager = new CountManager();
Producer producer = new Producer( countManager );
Consumer consumer = new Consumer( countManager );
// Schedule some work to be done on background threads.
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
ses.scheduleAtFixedRate( producer , 0 , 7 , TimeUnit.SECONDS );
ses.scheduleAtFixedRate( consumer , 0 , 3 , TimeUnit.SECONDS );
// Let the app run a while.
try { Thread.sleep( Duration.ofMinutes( 1 ).toMillis() ); } catch ( InterruptedException e ) {e.printStackTrace(); }
ses.shutdown();
System.out.println( "The app ended demo on thread # " + Thread.currentThread().getId() + " at " + Instant.now() );
}
}
When run:
The app began demo on thread # 1 at 2021-02-14T02:10:09.981741Z
CountManager constructor initialized the count to = 0 at 2021-02-14T02:10:10.003056Z
The producer in thread # 14 incremented newCount = 1 at 2021-02-14T02:10:10.014164Z
The consumer on thread # 14 reports currentCount = 1 at 2021-02-14T02:10:10.026975Z
The consumer on thread # 14 reports currentCount = 1 at 2021-02-14T02:10:13.019270Z
The consumer on thread # 14 reports currentCount = 1 at 2021-02-14T02:10:16.016694Z
The producer in thread # 14 incremented newCount = 2 at 2021-02-14T02:10:17.016730Z
The consumer on thread # 14 reports currentCount = 2 at 2021-02-14T02:10:19.017666Z
The consumer on thread # 14 reports currentCount = 2 at 2021-02-14T02:10:22.017923Z
The producer in thread # 14 incremented newCount = 3 at 2021-02-14T02:10:24.015972Z
The consumer on thread # 14 reports currentCount = 3 at 2021-02-14T02:10:25.018212Z
The consumer on thread # 14 reports currentCount = 3 at 2021-02-14T02:10:28.048834Z
The producer in thread # 14 incremented newCount = 4 at 2021-02-14T02:10:31.016746Z
The consumer on thread # 14 reports currentCount = 4 at 2021-02-14T02:10:31.016944Z
The consumer on thread # 14 reports currentCount = 4 at 2021-02-14T02:10:34.016674Z
The consumer on thread # 14 reports currentCount = 4 at 2021-02-14T02:10:37.017195Z
The producer in thread # 14 incremented newCount = 5 at 2021-02-14T02:10:38.016650Z
The consumer on thread # 14 reports currentCount = 5 at 2021-02-14T02:10:40.017862Z
The consumer on thread # 14 reports currentCount = 5 at 2021-02-14T02:10:43.016564Z
The producer in thread # 14 incremented newCount = 6 at 2021-02-14T02:10:45.017760Z
The consumer on thread # 14 reports currentCount = 6 at 2021-02-14T02:10:46.016909Z
The consumer on thread # 14 reports currentCount = 6 at 2021-02-14T02:10:49.014339Z
The producer in thread # 14 incremented newCount = 7 at 2021-02-14T02:10:52.013271Z
The consumer on thread # 14 reports currentCount = 7 at 2021-02-14T02:10:52.013389Z
The consumer on thread # 14 reports currentCount = 7 at 2021-02-14T02:10:55.017526Z
The consumer on thread # 14 reports currentCount = 7 at 2021-02-14T02:10:58.017050Z
The producer in thread # 14 incremented newCount = 8 at 2021-02-14T02:10:59.014334Z
The consumer on thread # 14 reports currentCount = 8 at 2021-02-14T02:11:01.013358Z
The consumer on thread # 14 reports currentCount = 8 at 2021-02-14T02:11:04.014889Z
The producer in thread # 14 incremented newCount = 9 at 2021-02-14T02:11:06.015598Z
The consumer on thread # 14 reports currentCount = 9 at 2021-02-14T02:11:07.014802Z
The consumer on thread # 14 reports currentCount = 9 at 2021-02-14T02:11:10.017313Z
The app ended demo on thread # 1 at 2021-02-14T02:11:10.017975Z
class ThreadDemo extends Thread
{
public void run()
{
for(int i =0; i<5;i++)
{
System.out.println(i);
}
}
}
class ThreadApp
{
public static void main(String args[])
{
ThreadDemo thread1 = new ThreadDemo();
thread1.start();
ThreadDemo thread2 = new ThreadDemo();
thread2.start();
ThreadDemo thread3 = new ThreadDemo();
thread3.start();
}
}
Output:
0
2
3
1
4
1
2
4
3
0
0
1
2
3
4
By default, Java applications are single thread application. We are going for the concept called multithreading to share the work. Means, instead of doing the work with one thread (Main thread) if we create the thread, then it will simplifies the work. I understood this thing theoretically. My doubt arises when I start coding.
In the above program I have created 3 threads. If 3 threads are working on the same logic (iteration and printing the values by using for loop) why it is giving 3 separate output instead of giving one set of values from 0 to 4?
Duplicating the work, not sharing the work
You said:
We are going for the concept called multithreading to share the work.
But you did not share the work. You repeated the work. Instead of running a for loop once, you ran a for loop three times, once in each of three threads.
You asked:
why it is giving 3 separate output instead of giving one set of values from 0 to 4?
If a school teacher asks each of three pupils to write the alphabet on the board, we would end up not with 26 letters but with 78 letters (3 * 26). Each pupil would be looping through the letters of the alphabet. Likewise, each of your three threads looped through the count of zero to four.
Your for loop is local to (within) your task code. So each thread runs all of that code starting at the top. So the for loop executes three times, one per thread.
Beware: System.out prints out-of-order
Sending text to System.out via calls to println or print does not result in text appearing immediately and in the order sent. The lines of text you send may appear out of order.
When examining a sequence of statements, always include a timestamp such as java.time.Instant.now(). Then study the output. You may need to manually re-order the outputs using a text editor to see the true sequence.
You can see the out-of-chronological-order lines in my own example output below.
Executor service
In modern Java we no longer need address the Thread class directly. Generally best to use the Executors framework. See Oracle Tutorial.
package work.basil.example;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Counter
{
public static void main ( String[] args )
{
ExecutorService executorService = Executors.newCachedThreadPool();
Runnable task = new Runnable()
{
#Override
public void run ( )
{
for ( int i = 0 ; i < 5 ; i++ )
{
System.out.println( "i = " + i + " at " + Instant.now() + " on thread " + Thread.currentThread().getName() );
}
}
};
executorService.submit( task );
executorService.submit( task );
executorService.submit( task );
// Let our program run a while, then gracefully shutdown the executor service.
// Otherwise the backing thread pool may run indefinitely, like a zombie 🧟.
try { Thread.sleep( Duration.ofSeconds( 5 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }
executorService.shutdown();
try { executorService.awaitTermination( 1 , TimeUnit.MINUTES ); } catch ( InterruptedException e ) { e.printStackTrace(); }
}
}
When run.
i = 0 at 2021-01-06T05:28:34.349290Z on thread pool-1-thread-1
i = 1 at 2021-01-06T05:28:34.391997Z on thread pool-1-thread-1
i = 0 at 2021-01-06T05:28:34.349246Z on thread pool-1-thread-2
i = 0 at 2021-01-06T05:28:34.349464Z on thread pool-1-thread-3
i = 1 at 2021-01-06T05:28:34.392467Z on thread pool-1-thread-3
i = 2 at 2021-01-06T05:28:34.392162Z on thread pool-1-thread-1
i = 2 at 2021-01-06T05:28:34.392578Z on thread pool-1-thread-3
i = 3 at 2021-01-06T05:28:34.392670Z on thread pool-1-thread-1
i = 3 at 2021-01-06T05:28:34.392773Z on thread pool-1-thread-3
i = 4 at 2021-01-06T05:28:34.393165Z on thread pool-1-thread-3
i = 1 at 2021-01-06T05:28:34.392734Z on thread pool-1-thread-2
i = 4 at 2021-01-06T05:28:34.392971Z on thread pool-1-thread-1
i = 2 at 2021-01-06T05:28:34.395138Z on thread pool-1-thread-2
i = 3 at 2021-01-06T05:28:34.396407Z on thread pool-1-thread-2
i = 4 at 2021-01-06T05:28:34.397002Z on thread pool-1-thread-2
Project Loom
Project Loom promises to be bring to Java new features such as virtual threads (fibers), and making ExecutorService be AutoCloseable for use with try-with-resources to automatically shutdown.
Let's rewrite the above code to use Project Loom technologies. Preliminary builds are available now based on early-access Java 16.
Also, we can rewrite the anonymous class seen above with simpler lambda syntax.
Another difference from above: Virtual threads do not have a name. So we switch to using the id number of the thread to differentiate between threads running.
try
(
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
Runnable task = ( ) -> {
for ( int i = 0 ; i < 5 ; i++ )
{
System.out.println( "i = " + i + " at " + Instant.now() + " on thread " + Thread.currentThread().getId() );
}
};
executorService.submit( task );
executorService.submit( task );
executorService.submit( task );
}
// At this point, the flow-of-control blocks until all submitted tasks are done.
// And the executor service is also shutdown by this point.
When run.
i = 0 at 2021-01-06T05:41:36.628800Z on thread 17
i = 1 at 2021-01-06T05:41:36.647428Z on thread 17
i = 2 at 2021-01-06T05:41:36.647626Z on thread 17
i = 3 at 2021-01-06T05:41:36.647828Z on thread 17
i = 4 at 2021-01-06T05:41:36.647902Z on thread 17
i = 0 at 2021-01-06T05:41:36.628842Z on thread 14
i = 1 at 2021-01-06T05:41:36.648148Z on thread 14
i = 2 at 2021-01-06T05:41:36.648227Z on thread 14
i = 3 at 2021-01-06T05:41:36.648294Z on thread 14
i = 4 at 2021-01-06T05:41:36.648365Z on thread 14
i = 0 at 2021-01-06T05:41:36.628837Z on thread 16
i = 1 at 2021-01-06T05:41:36.648839Z on thread 16
i = 2 at 2021-01-06T05:41:36.648919Z on thread 16
i = 3 at 2021-01-06T05:41:36.648991Z on thread 16
i = 4 at 2021-01-06T05:41:36.649054Z on thread 16
Sharing state across threads
If you really wanted to share values across the threads, you define them outside the immediate task code.
In this next example, we define a class Counter that implements Runnable. As a Runnable we can pass an instance of this class to an executor service. We defined a member field, a ConcurrentMap (a thread-safe Map) that tracks each of our desired numbers 0-4. For each of those five numbers, we map to the id number of the virtual thread that was able to beat the other virtual threads to submitting that entry into our originally-empty map.
Be aware that we are submitting a single Counter object to all three threads. So all three threads have access to the very same ConcurrentMap object. That is why we must use a ConcurrentMap rather than a plain Map. Any resource shared across threads must be built to be thread-safe.
We are calling Thread.sleep to try to mix things up. Otherwise, the first thread might finish all the work while the main thread is still submitting to the second and third threads.
package work.basil.example;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.*;
public class Counter implements Runnable
{
public ConcurrentMap < Integer, Long > results = new ConcurrentHashMap <>();
#Override
public void run ( )
{
try { Thread.sleep( Duration.ofMillis( 100 ) ); } catch ( InterruptedException e ) { e.printStackTrace(); }
Long threadId = Thread.currentThread().getId(); // ID of this thread.
for ( int i = 0 ; i < 5 ; i++ )
{
// Shake things up by waiting some random time.
try { Thread.sleep( Duration.ofMillis( ThreadLocalRandom.current().nextInt(1, 100) ) ); } catch ( InterruptedException e ) { e.printStackTrace(); }
results.putIfAbsent( i , threadId ); // Auto-boxing converts the `int` value of `i` to be wrapped as a `Integer` object.
}
}
}
Here is a main method to make our exercise happen.
public static void main ( String[] args )
{
Counter counter = new Counter();
try
(
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
executorService.submit( counter );
executorService.submit( counter );
executorService.submit( counter );
}
// At this point, the flow-of-control blocks until all submitted tasks are done.
// And the executor service is also shutdown by this point.
System.out.println( "counter.results = " + counter.results );
}
In the results of this particular run, we can see that the two threads number 16 and 17 had all the success in putting entries into our map. The third thread was not able to be the first to put in any of the five entries.
counter.results = {0=16, 1=17, 2=17, 3=16, 4=16}
Try to do some various tests and see by yourself what is coming and from where
public class ThreadApp {
public static void main(String args[]) throws InterruptedException {
ThreadDemo thread1 = new ThreadApp().new ThreadDemo("t1",4);
ThreadDemo thread2 = new ThreadApp().new ThreadDemo("t2",7);
thread2.start();
thread1.start();
ThreadDemo thread3 = new ThreadApp().new ThreadDemo("t3",2);
// wait till t1 &t2 finish run then launch t3
thread1.join();
thread2.join();
thread3.start();
}
class ThreadDemo extends Thread {
int stop;
public ThreadDemo(String name, int stop) {
super(name);
this.stop = stop;
}
public void run() {
for (int i = 0; i < stop; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
}
Possible Output:
t2:0
t2:1
t1:0
t2:2
t1:1
t2:3
t1:2
t2:4
t1:3
t2:5
t2:6
//due to join t3 start only after t1 & t2 finish their run
t3:0
t3:1
Related to benefits, just one hint Producer-Consumer problem ...
I am having following code:
public class ExecFramework implements Runnable {
int i;
public ExecFramework() {
}
public ExecFramework(int i) {
this.i = i;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService pool=new ThreadPoolExecutor(2, 10, 5000,TimeUnit.SECONDS, new ArrayBlockingQueue(2));
for (int i = 0; i < 20; i++) {
Runnable obj=new ExecFramework(i);
pool.execute(obj);
}
pool.shutdown();
while(pool.isTerminated()){
System.out.println("ExecutorService is terminated");
}
}
}
Is my knowledge for the way ThreadPoolExecutor works correct:
If NumberOfThreadRunning < CoreNumberOfThreads then ThreadPoolExecutor creates a new thread to complete the task.
If NumberOfThreadRunning > CoreNumberOfThreads then queue this task in BlockingQueue but if queue is full then create a new Thread only if
NumberOfThread < MaxNumberOfThreads.
Once the task is completed thread running that task is available for other task.
According to 3rd point. I should be able to execute 20 tasks using ThreadPoolExecutor.
Why Output of above code is?
pool-1-thread-5 6
pool-1-thread-4 5
pool-1-thread-3 4
pool-1-thread-1 0
pool-1-thread-2 1
pool-1-thread-6 7
pool-1-thread-7 8
pool-1-thread-8 9
pool-1-thread-9 10
pool-1-thread-10 11
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task executionFramework.ExecFramework#232204a1 rejected from java.util.concurrent.ThreadPoolExecutor#4aa298b7[Running, pool size = 10, active threads = 10, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at executionFramework.ExecFramework.main(ExecFramework.java:88)
pool-1-thread-8 2
pool-1-thread-6 3
The documentation says it: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
In section Rejected tasks.
In your case i would guess this :
when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated
happens.