Continuing with execution flow of Futures and thread. See Part 1 if you have not. I would like to highlight when future.get() completes in different scenarios. Firstly I know it is not best practice to use Futures in this way but I think it is still important to understand how the internals work.
Firstly I will do a basic Async (Future.thenApplyAsync) example, nothing special here.
package com.test ;
import java.util.concurrent.CompletableFuture ;
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
import java.util.concurrent.ThreadFactory ;
import java.util.concurrent.atomic.AtomicInteger ;
public class Test21Future {
public static void main ( String [] args ) {
Thread th1 = new Thread (() -> {
try {
task ();
} catch ( Exception e ) {
e . printStackTrace ();
}
}, "master thread" );
th1 . start ();
}
public static void task () throws InterruptedException , ExecutionException {
ExecutorService exeService = Executors . newFixedThreadPool ( 6 , new ThreadFactory () {
final AtomicInteger counter = new AtomicInteger ();
@Override
public Thread newThread ( Runnable r ) {
return new Thread ( r , "PoolThread : " + counter . getAndIncrement ());
}
});
CompletableFuture < String > future1 = CompletableFuture . supplyAsync (() -> {
try {
sleep ( 1000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 1 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return "A" ;
}, exeService );
CompletableFuture < String > future2 = future1 . thenApplyAsync ( a -> {
try {
sleep ( 2000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 2 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return a + "B" ;
}, exeService );
CompletableFuture < String > future3 = future2 . thenApplyAsync ( a -> {
try {
sleep ( 2000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 3 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return a + "C" ;
}, exeService );
System . out . println ( "Starting the wait..." );
System . out . println ( "Future 1 Get : " + future1 . get ());
System . out . println ( "Future 2 Get : " + future2 . get ());
System . out . println ( "Future 3 Get : " + future3 . get ());
}
private static void sleep ( long time ) {
try {
Thread . sleep ( time );
} catch ( InterruptedException e ) {
// TODO Auto-generated catch block
e . printStackTrace ();
}
}
}
Below is the output for the execution of above.
Starting the wait ...
Future 1 Return in Thread [ PoolThread : 0 ]
Future 1 Get : A
Future 2 Return in Thread [ PoolThread : 1 ]
Future 2 Get : AB
Future 3 Return in Thread [ PoolThread : 2 ]
Future 3 Get : ABC
Now as you expected as soon as the Future completes the get() block is removed and the code progresses. Also note how each future executes in a different thread since we use the Async method as discussed in Part 1.
Now things get interesting when you do not use Async (Future.thenApply()). Here is a example.
package com.test ;
import java.util.concurrent.CompletableFuture ;
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
import java.util.concurrent.ThreadFactory ;
import java.util.concurrent.atomic.AtomicInteger ;
public class Test21Future {
public static void main ( String [] args ) {
Thread th1 = new Thread (() -> {
try {
task ();
} catch ( Exception e ) {
e . printStackTrace ();
}
}, "master thread" );
th1 . start ();
}
public static void task () throws InterruptedException , ExecutionException {
ExecutorService exeService = Executors . newFixedThreadPool ( 6 , new ThreadFactory () {
final AtomicInteger counter = new AtomicInteger ();
@Override
public Thread newThread ( Runnable r ) {
return new Thread ( r , "PoolThread : " + counter . getAndIncrement ());
}
});
CompletableFuture < String > future1 = CompletableFuture . supplyAsync (() -> {
try {
sleep ( 1000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 1 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return "A" ;
}, exeService );
CompletableFuture < String > future2 = future1 . thenApply ( a -> {
try {
sleep ( 2000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 2 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return a + "B" ;
});
CompletableFuture < String > future3 = future2 . thenApply ( a -> {
try {
sleep ( 2000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 3 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return a + "C" ;
});
CompletableFuture < String > future4 = future3 . thenApply ( a -> {
try {
sleep ( 2000L );
} catch ( Exception e ) {
e . printStackTrace ();
}
System . out . println ( "Future 4 Return in Thread [" + Thread . currentThread (). getName () + "]" );
return a + "D" ;
});
System . out . println ( "Starting the wait..." );
System . out . println ( "Future 1 Get : " + future1 . get ());
System . out . println ( "Future 2 Get : " + future2 . get ());
System . out . println ( "Future 3 Get : " + future3 . get ());
System . out . println ( "Future 4 Get : " + future4 . get ());
}
private static void sleep ( long time ) {
try {
Thread . sleep ( time );
} catch ( InterruptedException e ) {
// TODO Auto-generated catch block
e . printStackTrace ();
}
}
}
Below is the output:
Starting the wait ...
Future 1 Return in Thread [ PoolThread : 0 ]
Future 1 Get : A
Future 2 Return in Thread [ PoolThread : 0 ]
Future 3 Return in Thread [ PoolThread : 0 ]
Future 4 Return in Thread [ PoolThread : 0 ]
Future 2 Get : AB
Future 3 Get : ABC
Future 4 Get : ABCD
As you can see here Future 2, 3 and 4 block on the get call until the last future (Future 4) completes. Future 1 will unblock on the get as expected since it is a Async call but since Future 2, 3 and 4 is not they will block.
It is a bit of a oddity to note about Futures.