The other Answers are correct, with important points. In addition, let me show how future technology being developed in Project Loom will simplify such code.
Project Loom
Project Loom will be bringing some changes to Java. Experimental builds of Loom technology, based on early-access Java 17, are available now. The Loom team is soliciting feedback.
AutoCloseable
and try-with-resources
One change is that ExecutorService
extends AutoCloseable
. This means we can use try-with-resources syntax to conveniently and automatically close the service after the try-block completes.
Block until submitted tasks are done
Furthermore, the flow-of-control blocks on that try-block until all the submitted tasks are done/failed/canceled. No need to track progress of the individual tasks, unless you care to.
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
… submit tasks to the executor service, to be run on background threads.
}
// At this point, all submitted are done/failed/canceled.
// At this point, the executor service is automatically being shut down.
AtomicInteger
As others said, your use of int
primitives for a
& b
variables across threads may fail because of visibility issues in the Java Memory Model. One option is to mark them as volatile
.
I prefer the alternative, using the Atomic…
classes. Replace those int
vars with AtomicInteger
objects to wrap the incrementing count number.
Mark those member fields final
so each instance is never replaced.
// Member fields
final AtomicInteger a, b;
// Constructor
public Incrementor ( )
{
this.a = new AtomicInteger();
this.b = new AtomicInteger();
}
To increment by one the value within the AtomicInteger
, we call incrementAndGet
. This call returns the new incremented number. So I altered the signature of your add methods to show that we can return the new value, if ever needed.
// Logic
public int addA ( )
{
return this.a.incrementAndGet();
}
public int addB ( )
{
return this.b.incrementAndGet();
}
Virtual threads
Another feature coming in Project Loom is virtual threads, also known as fibers. Many of these lightweight threads are mapped to run on platform/kernel threads. If your code often blocks, then using virtual threads will dramatically speed up performance of your app. Use the new feature by calling Executors.newVirtualThreadExecutor
.
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{ … }
Example class
I have written a class named Incrementor
to be similar to yours. Using such a class looks like this:
Incrementor incrementor = new Incrementor();
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
for ( int i = 0 ; i < 10 ; i++ )
{
executorService.submit( ( ) -> {
int newValueA = incrementor.addA();
int newValueB = incrementor.addB();
System.out.println( "Thread " + Thread.currentThread().getId() + " incremented a & b to: " + newValueA + " & " + newValueB + " at " + Instant.now() );
} );
}
}
Coordinating a
& b
Caveat: As others commented, such code does not atomically increment both a
& b
together in synch. Apparently that is not a need of yours, so I ignore the issue. You can see that behavior in action, in the example run output shown at bottom below, where two threads interleaved during their access to a
& b
. Excerpted here:
Thread 24 incremented a & b to: 10 & 9 at 2021-02-09T02:21:30.270246Z
Thread 23 incremented a & b to: 9 & 10 at 2021-02-09T02:21:30.270246Z
Full class code
Pull together all this code.
Notice the simplicity of the code when (a) under Loom, and (b) using Atomic…
constants. No need for semaphores, latches, CompletableFuture
, nor calling ExecutorService#shutdown
.
package work.basil.example;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Incrementor
{
// Member fields
final AtomicInteger a , b ;
// Constructor
public Incrementor ( )
{
this.a = new AtomicInteger();
this.b = new AtomicInteger();
}
// Logic
public int addA ( )
{
return this.a.incrementAndGet();
}
public int addB ( )
{
return this.b.incrementAndGet();
}
}
And a main
method to demonstrate using that class.
public static void main ( String[] args )
{
// Exercise this class by instantiating, then incrementing ten times.
System.out.println( "INFO - `main` starting the demo. " + Instant.now() );
Incrementor incrementor = new Incrementor();
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
for ( int i = 0 ; i < 10 ; i++ )
{
executorService.submit( ( ) -> {
int newValueA = incrementor.addA();
int newValueB = incrementor.addB();
System.out.println( "Thread " + Thread.currentThread().getId() + " incremented a & b to: " + newValueA + " & " + newValueB + " at " + Instant.now() );
} );
}
}
System.out.println( "INFO - At this point all submitted tasks are done/failed/canceled, and executor service is shutting down. " + Instant.now() );
System.out.println( "incrementor.a.get() = " + incrementor.a.get() );
System.out.println( "incrementor.b.get() = " + incrementor.b.get() );
System.out.println( "INFO - `main` ending. " + Instant.now() );
}
When run.
INFO - `main` starting the demo. 2021-02-09T02:21:30.173816Z
Thread 18 incremented a & b to: 4 & 4 at 2021-02-09T02:21:30.245812Z
Thread 14 incremented a & b to: 1 & 1 at 2021-02-09T02:21:30.242306Z
Thread 20 incremented a & b to: 6 & 6 at 2021-02-09T02:21:30.246784Z
Thread 21 incremented a & b to: 8 & 8 at 2021-02-09T02:21:30.269666Z
Thread 22 incremented a & b to: 7 & 7 at 2021-02-09T02:21:30.269666Z
Thread 17 incremented a & b to: 3 & 3 at 2021-02-09T02:21:30.243580Z
Thread 24 incremented a & b to: 10 & 9 at 2021-02-09T02:21:30.270246Z
Thread 23 incremented a & b to: 9 & 10 at 2021-02-09T02:21:30.270246Z
Thread 16 incremented a & b to: 2 & 2 at 2021-02-09T02:21:30.242335Z
Thread 19 incremented a & b to: 5 & 5 at 2021-02-09T02:21:30.246646Z
INFO - At this point all submitted tasks are done/failed/canceled, and executor service is shutting down. 2021-02-09T02:21:30.279542Z
incrementor.a.get() = 10
incrementor.b.get() = 10
INFO - `main` ending. 2021-02-09T02:21:30.285862Z