You are on page 1of 24

Introduction to multi-threading programming with Java

(notes)

Miguel Angel Piña Avelino


February 3, 2023

Prerequisites to take the course:


• Basic knowledge about Maven

• An editor like emacs or vi or use an IDE like Netbeans


• JDK 17 (open-jdk)
• git

1 Introduction
In the early 2000s, the multicore revolution began due to was difficulty built processor chips
smaller and faster. Derived from this situation, we had to change the form we developing soft-
ware. The multicore chips cause that performs computing to be more effective by exploiting
“parallelism”. However, the challenge is in how to exploit that parallelism. Those multicore chips
(multiprocessors) usually use shared memory to communicate the processors between themselves.
An important aspect when programming these multiprocessors is establishing coordination mech-
anisms to access shared memory, for example, how to synchronize the access to shared data to
avoid problems while it is manipulated (writes and lectures). The above is challenging because
modern systems are inherently asynchronous, and without synchronization mechanisms, unpre-
dictable events can occur while shared data is modified concurrently.
In this tutorial, we will focus on tools and techniques for programming multiprocessors us-
ing shared memory with Java. We will cover some topics related to concurrent and parallel
computing.

2 Processes and threads


What is the difference between a process and a thread? Both process and thread are independent
sequences of execution. Loosely speaking, in practical terms, a process is an executing program.
A thread is a lightweight process that can run over parallel with other threads and share resources
as memory and disc with its parent process. Usually, threads run in the process space context.
For example, we can run a program in java (like Netbeans), and the program in is known
as the main process. During execution of program, it can perform multiple events like calls to
internet services or call to other programs. Commonly, these events are performed in threads to
avoid freeze the main thread and are called asynchronously.

1
3 Basics
Creating the base project: Let’s create a maven based project. In a terminal, write:
1 cd ..
2 mvn archetype:generate -DgroupId=mx.unam.concurrent \
3 -DartifactId=concurrent-example \
4 -DarchetypeArtifactId=maven-archetype-quickstart \
5 -DarchetypeVersion=1.4 -DinteractiveMode=false
6 ls | grep concurrent-example

Code 3.1: Generating project with maven


It creates a project with a main file called App.java. We will use that file to write all our
code for multi-threaded applications. The first thing to do is change some parameters of our
project to work with a recent java version.
In the file pom.xml, change the target output and compiler version. Those values should be
changed in the properties section as shown in code 3.2.
1 <maven.compiler.source>17</maven.compiler.source>
2 <maven.compiler.target>17</maven.compiler.target>

Code 3.2: Changing content of file pom.xml


However, our application still not have defined a main class used to execute the program. To
allow this, add the code shown in code 3.3 to the pom.xml, in the plugins section.
1 <plugin>
2 <groupId>org.codehaus.mojo</groupId>
3 <artifactId>exec-maven-plugin</artifactId>
4 <version>1.2.1</version>
5 <executions>
6 <execution>
7 <goals>
8 <goal>java</goal>
9 </goals>
10 </execution>
11 </executions>
12 <configuration>
13 <mainClass>mx.unam.concurrent.App</mainClass>
14 </configuration>
15 </plugin>

Code 3.3: Adding a plugin to execute directly from maven


We can run our project with the following instruction:
1 pwd
2 cd ../concurrent-example
3 mvn compile exec:java

Code 3.4: Example of how to execute the project

4 Creating and starting a Thread


In Java, to use threads in our applications, we can create an instance of the class Thread
(java.lang.Thread) or make a derived subclass. Also we can provide an object that im-
plements the Runnable interface (java.lang.Runnable). This interface defines a single

2
method, run, meant to contain the code executed in the thread. Lets create a basic application
where we define an instance of Thread and run it.
1 public class App {
2
3 public static void main(String[] args) {
4 MyThread1 obj1 = new MyThread1();
5 MyThread2 obj2 = new MyThread2();
6 Thread t = new Thread(new MyRunnable());
7
8 obj1.start();
9 obj2.start();
10 t.start();
11 }
12 }
13
14 class MyThread1 extends Thread {
15 @Override
16 public void run() {
17 System.out.println("Thread 1 is running");
18 }
19 }
20
21 class MyThread2 extends Thread {
22 @Override
23 public void run() {
24 System.out.println("Thread 2 is running");
25 }
26 }
27
28 class MyRunnable implements Runnable {
29 @Override
30 public void run() {
31 System.out.println("My runnable object is running");
32 }
33 }

Code 4.1: Basic example of how to create threads

A more interesting example could be the following:


1 package mx.unam.concurrent;
2
3 public class App {
4
5 public static void main(String[] args) {
6 MyThread1 obj1 = new MyThread1();
7 MyThread2 obj2 = new MyThread2();
8 Thread t = new Thread(new MyRunnable());
9 obj1.start();
10 obj2.start();
11 t.start();
12 }
13 }
14
15 class MyThread1 extends Thread {
16 @Override
17 public void run() {
18 for (int i = 0; i < 10; i++) {
19 String output = String.format("Thread 1 is running. Iter: %d", i);
20 System.out.println(output);
21 }

3
22 }
23 }
24
25 class MyThread2 extends Thread {
26 @Override
27 public void run() {
28 for (int i = 0; i < 10; i++) {
29 String output = String.format("Thread 2 is running. Iter: %d", i);
30 System.out.println(output);
31 }
32 }
33 }
34
35 class MyRunnable implements Runnable {
36 @Override
37 public void run() {
38 for (int i = 0; i < 10; i++) {
39 String output = String
40 .format("My runnable object is running. Iter: %d", i);
41 System.out.println(output);
42 }
43 }
44 }

Code 4.2: Second example of how to create threads

A possible output for the previous code is the following. We can observe how the calls to
the println method are interspersed. In a sequential execution, the output of the object obj1
should be printed (the sequence of printlns from zero to nine) followed by the output of the
obj2 (the sequence of printlns from zero to nine) and similarly, in the end, the output from the
object t, but, in this, there is not a order in how the objects are called.

5 Thread managment
After seeing how to use threads in a basic way, now let us discuss some methods available to
thread management. More documentation about these methods is available on https://docs.
oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Thread.html. The
methods that we refer are:

• start
• suspend

• stop
• sleep
• join

We will exemplify the use of the first four methods using the program shown in the code 5.1.
1 public class App {
2 public static void main(String[] args) {
3 MyThread t1 = new MyThread("First Thread");
4 MyThread t2 = new MyThread("Second Thread");
5 try {
6 Thread.sleep(500); // Sleeping for 500ms

4
7 t1.stop();
8 t2.stop();
9 Thread.sleep(500);
10 }
11 catch (InterruptedException e) {
12 System.out.format("Interrupted Exception: %s\n",
13 e.getMessage());
14 e.printStackTrace();
15 }
16 System.out.println("Exiting the main thread");
17 }
18 }
19 class MyThread implements Runnable {
20 private boolean exit;
21 private String name;
22 Thread t;
23
24 public MyThread(String threadName) {
25 name = threadName;
26 t = new Thread(this, name);
27 System.out.format("New Thread: %s\n", t.toString());
28 exit = false;
29 t.start(); // Starting the thread
30 }
31
32 @Override
33 public void run() {
34 int i = 0;
35 while (!exit) {
36 System.out.format("%s: %d\n", name, i);
37 i++;
38 try {
39 Thread.sleep(100); // Sleeping for 100ms
40 }
41 catch (InterruptedException e) {
42 System.out.format("Interrupted Exception: %s\n",
43 e.getMessage());
44 e.printStackTrace();
45 }
46 }
47 }
48
49 public void stop() {
50 exit = true;
51 }
52 }

Code 5.1: Code example using methods for control threads.

This program declares an inner class called MyThread, which implements the Runnable
interface. The class constructor takes as a parameter a string, which represents the name for the
instance. Inside of the constructor, the instance declares a thread and starts it with the method
start(). This method will invoke the method run. In this method, it will print the name of
the instance with the value of a counter. The counter will increase it every 100 milliseconds. The
class MyThread also have a method stop, where we indicating when the method run should
stop.
Additionally, the App class will contain the main method. In this method, it will declare two
instances of class MyThread with distinct names. Then, the main thread will do the following:

• sleeps by 500 milliseconds

5
• calls the method stop of the two instances
• and then, it sleeps for another 500 milliseconds.

A possible output for the execution of the previous code is the following:

New Thread: Thread[First Thread,5,main]


New Thread: Thread[Second Thread,5,main]
First Thread: 0
Second Thread: 0
Second Thread: 1
First Thread: 1
Second Thread: 2
First Thread: 2
First Thread: 3
Second Thread: 3
Second Thread: 4
First Thread: 4
Exiting the main thread

The join() method allows one thread to wait until another thread completes its execution.
From Oracle’s documentation:

If t is a Thread object whose thread is currently executing, t.join() causes the


current thread pauses execution until t’s thread terminates.

Let’s look at a more elaborate example:


1 public class App {
2
3 static void threadMessage(String message) {
4 String threadName = Thread.currentThread().getName();
5 System.out.format("%s: %s%n", threadName, message);
6 }
7
8 private static class MessageLoop
9 implements Runnable {
10 public void run() {
11 String importantInfo[] = {
12 "Some content",
13 "Another String",
14 "Doing nothing",
15 "I’m close to finishing"
16 };
17 try {
18 for (int i = 0; i < importantInfo.length; i++) {
19 Thread.sleep(4000);
20 threadMessage(importantInfo[i]);
21 }
22 } catch (InterruptedException e) {
23 threadMessage("I wasn’t done!");
24 }
25 }
26 }
27
28 public static void main(String args[])
29 throws InterruptedException {

6
30
31 long patience = 1000 * 60 * 60;
32 if (args.length > 0) {
33 try {
34 patience = Long.parseLong(args[0]) * 1000;
35 } catch (NumberFormatException e) {
36 System.err.println("Argument must be an integer.");
37 System.exit(1);
38 }
39 }
40
41 threadMessage("Starting MessageLoop thread");
42 long startTime = System.currentTimeMillis();
43 Thread t = new Thread(new MessageLoop());
44 t.start();
45
46 threadMessage("Waiting for MessageLoop thread to finish");
47 while (t.isAlive()) {
48 threadMessage("Still waiting...");
49 t.join(1000);
50 if (((System.currentTimeMillis() - startTime) > patience)
51 && t.isAlive()) {
52 threadMessage("Tired of waiting!");
53 t.interrupt();
54 t.join();
55 }
56 }
57 threadMessage("Finally!");
58 }
59 }

Code 5.2: Example using join() method.

A possible output may be the following:

main: Starting MessageLoop thread


main: Waiting for MessageLoop thread to finish
main: Still waiting...
main: Still waiting...
main: Still waiting...
main: Still waiting...
Thread-0: Some content
main: Still waiting...
main: Still waiting...
main: Still waiting...
main: Still waiting...
Thread-0: Another String
main: Still waiting...
main: Still waiting...
main: Still waiting...
main: Still waiting...
Thread-0: Doing nothing
main: Still waiting...
main: Still waiting...
main: Still waiting...
main: Still waiting...

7
Thread-0: I’m close to finishing
main: Finally!

6 Executors
Sometimes, work directly with threads could be a bit difficult and can introduce some errors or
mistakes. To avoid this, the concurrent API of java provides a class called ExecutorService
(java.util.conccurent.ExecutorService). This class is capable of execute asynchronous
tasks and manage a pool of threads. Thus, we don’t have to create threads by hand. Also, the
threads in the pool can be reused throughout the life-cycle of our application.
The basic way to create an instance of ExecutorService is through the factory class
Executors (java.util.concurrent.Executors). This factory class provides many static
methods to create different instances. Variants of the instantiated class usually are parameterized
according the number of threads or the number of cores available. An small example is shown
in the code 6.1.
1 import java.util.concurrent.Executors;
2 import java.util.concurrent.ExecutorService;
3
4 public class App {
5
6 private static void doLongWork(String name) {
7 String message = String.format("Hello %s, how is going?", name);
8 System.out.println(message);
9 try {
10 Thread.sleep(1001);
11 }
12 catch (InterruptedException e) {
13 System.out.println("Error " + e.getMessage());
14 e.printStackTrace();
15 }
16 }
17
18 public static void main(String[] args) {
19 int numProcessors = Runtime.getRuntime().availableProcessors();
20 ExecutorService executor = Executors
21 .newFixedThreadPool(numProcessors);
22 for (int i = 0; i < numProcessors; i++) {
23 final int name = i;
24 executor.execute(() -> doLongWork(String.format("thread %d",
25 name)));
26 }
27 executor.shutdown();
28 }
29 }

Code 6.1: Using executors to manage threads and runnables.


In the main method of the code 6.1, we get the number of hardware threads available in our
machine. Then, we declare an instance of ExecutorService with a pool of k threads. Thereafter,
we launch a lambda function1 calling a method that it does some long work. For each core, we run
the lambda function. At the end of the program, we have to stop explicitly the ExecutorService,
if we did not do that, the service will keep listening for new tasks and never stops. Finally, we
get a result like shown below:
1 The lambda function is a Runnable. We can get the same result if we declare a class that implements the

interface and instantiate it. The instance is passed as argument to the method execute.

8
Hello thread 0, how is going?
Hello thread 1, how is going?
Hello thread 7, how is going?
Hello thread 6, how is going?
Hello thread 2, how is going?
Hello thread 4, how is going?
Hello thread 5, how is going?
Hello thread 3, how is going?

6.1 Callables and Futures


Like Runnable, executors can work with other kinds of tasks. We called these tasks callables
(Callable interface). Such interface is similar to Runnable, but instead of returning void after
calling its run method, it returns a value. These objects are parameters for the method submit
of the executorService. This method does not wait until the task completes. The executor
service cannot return the result of the callable objecty. In that case, the executor returns an
object of type Future. We can retrieve the computation result from the Callable object
using the Future objects. The futures have a method called isDone(), with this method we
can check if the future has already finished its execution. Another method available for futures
is get(). Calling this method blocks the current threads and waits until the callable completes
its execution. An example is below:
1 import java.util.concurrent.Callable;
2 import java.util.concurrent.Executors;
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.Future;
6 import java.util.concurrent.TimeUnit;
7
8 public class App {
9 public static void main(String args[]) throws ExecutionException {
10 Callable<Integer> task = () -> {
11 try {
12 TimeUnit.SECONDS.sleep(2);
13 return 42;
14 }
15 catch (InterruptedException e) {
16 System.out.println("Error " + e.getMessage());
17 e.printStackTrace();
18 }
19 return -1;
20 };
21
22 ExecutorService executor = Executors.newFixedThreadPool(1);
23 try {
24 Future<Integer> future = executor.submit(task);
25 System.out.printf("Future done? %b\n", future.isDone());
26 Integer result = future.get();
27 System.out.printf("Future done? %b\n", future.isDone());
28 System.out.printf("Result: %d\n", result);
29 executor.shutdown();
30 }
31 catch (InterruptedException | ExecutionException e) {
32 System.out.printf("Error %s\n", e.getMessage());
33 e.printStackTrace();
34 executor.shutdown();
35 }

9
36 }
37 }

Code 6.2: Basic example of callables and futures.

Future done? false


Future done? true
Result: 42

Calls to future.get() will block the current thread and wait until the computation finish.
But, sometimes this call can runs forever and making the program unresponsive. To counter-
attack this type of scenarios, you can add a timeout to avoid endless executions. An example
below:
1 import java.util.concurrent.Callable;
2 import java.util.concurrent.Executors;
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.Future;
6 import java.util.concurrent.TimeUnit;
7 import java.util.concurrent.TimeoutException;
8
9 public class App {
10 public static void main(String args[]) throws ExecutionException {
11 Callable<Integer> task = () -> {
12 try {
13 TimeUnit.SECONDS.sleep(4);
14 return 42;
15 }
16 catch (InterruptedException e) {
17 System.out.println("Error " + e.getMessage());
18 e.printStackTrace();
19 }
20 return -1;
21 };
22
23 ExecutorService executor = Executors.newFixedThreadPool(1);
24 try {
25 Future<Integer> future = executor.submit(task);
26 System.out.printf("Future done? %b\n", future.isDone());
27 Integer result = future.get(1, TimeUnit.SECONDS);
28 System.out.printf("Future done? %b\n", future.isDone());
29 System.out.printf("Result: %d\n", result);
30 executor.shutdown();
31 }
32 catch (InterruptedException | ExecutionException
33 | TimeoutException e) {
34 System.out.printf("Error %s\n", e.getLocalizedMessage());
35 e.printStackTrace();
36 executor.shutdown();
37 }
38 }
39 }

Code 6.3: Adding timeouts to avoid endless executions.

The last method we will cover of ExecutorService (there are a plenty more but we will not
cover them) is invokeAll. InvokeAll allows batch submitting of multiple callables. This
method accepts a collection of callables and returns a list of futures.

10
1 import java.util.concurrent.Callable;
2 import java.util.concurrent.Executors;
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.Future;
6 import java.util.concurrent.TimeUnit;
7
8 public class App {
9 public static void main(String args[]) throws ExecutionException {
10 try {
11 ExecutorService executor = Executors.newFixedThreadPool(1);
12
13 List<Callable<String>> callables = Arrays
14 .asList(
15 () -> {
16 TimeUnit.SECONDS.sleep(2);
17 return "task 1";
18 },
19 () -> {
20 TimeUnit.SECONDS.sleep(2);
21 return "task 2";
22 },
23 () -> {
24 TimeUnit.SECONDS.sleep(2);
25 return "task 3";
26 });
27
28 executor.invokeAll(callables)
29 .stream()
30 .map(future -> {
31 try {
32 return future.get();
33 } catch (Exception e) {
34 throw new IllegalStateException(e);
35 }
36 })
37 .forEach(System.out::println);
38 executor.shutdown();
39 }
40 catch (InterruptedException e) {
41 System.out.println("Error " + e.getMessage());
42 e.printStackTrace();
43 }
44 }
45 }

Code 6.4: Running tasks in batch

We can get better results if we increment the number of available threads for the executorSer-
vice. By example, using the previous instruction and setting it as the fixed value:
1 int numProcessors = Runtime.getRuntime().availableProcessors();
2 ExecutorService executor = Executors.newFixedThreadPool(numProcessors);

Code 6.5: Getting the total of hardware threads available in the machine.

It is not necessary call parallelStream from the Stream API because the executorService
assign each task to an available thread in its pool. But, depending on the method called (stream
or parallelStream), the order of the results may vary. This is due to stream (sequential) is
pipelined in a single thread instead of use multiple threads.

11
7 Synchronized and locks
7.1 Synchronized
When we implement a more complex multithreaded program, there are sections of code that
are accessed by multiple threads. We need to pay attention when accessing to shared mutable
variable concurrently. By example, lets think in a counter used by multiple threads concurrently.
A first approach could be the following:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class App {


public static void main(String[] args) {
Counter c = new Counter();
c.test();
}
}

class Counter {
int count = 0;
void increment() {
count = count + 1;
}
public void test() {
int numProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(numProcessors);
IntStream.range(0, 10000)
.forEach(i -> executor.submit(this::increment));
executor.shutdown();
System.out.println(count);
}
}

Code 7.1: Concurrent counter without synchronization

We can see that the value obtained is inconsistent with the value expected. This happen due
a race condition. To avoid this problem, java provides a simple mechanism to provide thread
synchronization, this through the keyword synchronized. Internally, Java uses a monitor lock
to manage synchronization. Let see an example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class App {

public static void main(String[] args) throws InterruptedException {


Counter c = new Counter();
c.test();
}
}
class Counter {
int count = 0;
synchronized void increment() {
count = count + 1;
}
public void test() throws InterruptedException {

12
int numProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors
.newFixedThreadPool(numProcessors);
IntStream.range(0, 10000)
.forEach(i -> executor.submit(this::increment));

stop(executor);
System.out.println(count);
}

public static void stop(ExecutorService executor) {


try {
executor.shutdown();
// give it time to finish
executor.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
ex.printStackTrace();
} finally {
if (!executor.isTerminated()) {
System.out.println("Termination interrupted");
}
executor.shutdown();
}
}
}

Code 7.2: Using the keyword synchronized to provide basic synchronization.

There are more uses to the keyword synchronized, but we do not discuss them in this tutorial.

7.2 Locks
Another tool provide by Java to manage synchronization are the looks. With them, we can set
lock mechanisms in an explicit way. Locks support many methods for finer grained synchroniza-
tion control. Java provides many types of locks, by example:
• ReentrantLock,
• ReentrantReadWriteLock or
• StampedLock.

Locks that implement the interface java.util.concurrent.locks.Lock, use the meth-


ods lock() and unlock() to provide locking explicitly. Another important operation provided
by this interface is tryLock(), used to try get the lock without pausing the current thread.
ReentrantLock is a class that implements the Lock interface.
Let see an example:
1 import java.util.concurrent.Executors;
2 import java.util.concurrent.ExecutorService;
3 import java.util.concurrent.TimeUnit;
4 import java.util.concurrent.locks.ReentrantLock;
5
6 public class App {
7
8 public static void main(String[] args) throws InterruptedException {
9 int numProcessors = Runtime.getRuntime().availableProcessors();
10 ExecutorService executor = Executors
11 .newFixedThreadPool(numProcessors);

13
12 ReentrantLock lock = new ReentrantLock();
13 executor.submit(() -> {
14 lock.lock();
15 try {
16 TimeUnit.SECONDS.sleep(4);
17 System.out.println("wake up");
18 } catch (InterruptedException ex) {
19 ex.printStackTrace();
20 } finally {
21 lock.unlock();
22 }
23 });
24 executor.submit(() -> {
25 System.out.printf("Locked: %b\n", lock.isLocked());
26 System.out.printf("Held by me: %b\n",
27 lock.isHeldByCurrentThread());
28 boolean locked = lock.tryLock();
29 System.out.printf("Lock aquired: %b\n", locked);
30 });
31 executor.shutdown();
32 }
33 }

Code 7.3: ReentrantLock example.


The class ReentrantReadWriteLock is another lock class that implements the interface
ReadWriteLock. The idea behind is to have a pair of locks for read-and-write access. Usually,
it is safe to read mutable shared variables while nobody is writing over them. Thus, the read-
lock can be held by many threads while no thread holds the write-lock. Using this lock, we can
improve performance and throughput when reads are more frequent than writes. An example is
below:
1 import java.util.concurrent.Executors;
2 import java.util.concurrent.ExecutorService;
3 import java.util.concurrent.TimeUnit;
4 import java.util.concurrent.locks.ReentrantLock;
5 import java.util.concurrent.locks.ReentrantReadWriteLock;
6 import java.util.concurrent.locks.ReadWriteLock;
7 import java.util.Map;
8 import java.util.HashMap;
9
10 public class App {
11
12 public static void main(String[] args) throws InterruptedException {
13 int numProcessors = Runtime.getRuntime().availableProcessors();
14 ExecutorService executor = Executors.
15 newFixedThreadPool(numProcessors);
16 Map<String, String> map = new HashMap<>();
17 ReadWriteLock lock = new ReentrantReadWriteLock();
18 executor.submit(() -> {
19 lock.writeLock().lock();
20 try {
21 System.out.println("Putting information into the map");
22 TimeUnit.SECONDS.sleep(4);
23 map.put("foo", "bar");
24 } catch (InterruptedException ex) {
25 ex.printStackTrace();
26 } finally {
27 lock.writeLock().unlock();
28 }
29 });

14
30
31 Runnable readTask = () -> {
32 lock.readLock().lock();
33 try {
34 String threadName = Thread.currentThread().getName();
35 System.out.printf("Name %s, value: %s\n",
36 threadName, map.get("foo"));
37 TimeUnit.SECONDS.sleep(1);
38 } catch(InterruptedException ex) {
39 ex.printStackTrace();
40 } finally {
41 lock.readLock().unlock();
42 }
43 };
44
45 executor.submit(readTask);
46 executor.submit(readTask);
47
48 executor.shutdown();
49 }
50 }

Code 7.4: ReentrantReadWriteLock example.

8 Atomic Variables
Java provides many classes to perform atomic operations over multiple data types. Inside of
these classes, the atomic operations are handled by compare-and-swap or swap (getAndSet)
instructions, which are atomic instructions directly supported by most modern CPUs. These
classes lives in the package java.util.concurrent.atomic and some examples are:

• AtomicInteger

• AtomicLong
• AtomicReference
• AtomicStampedReference
• AtomicIntegerArray

The specification for the atomic accesses performed by these classes is described in the docu-
mentation of java.lang.invoke.VarHandle (for java 9+). Some methods that they share
are:

• compareAndExchange

• compareAndSet
• getAndIncrement
• getAndSet

Let’s see an example of how use the atomic variables:

15
1 import java.util.concurrent.Executors;
2 import java.util.concurrent.ExecutorService;
3 import java.util.concurrent.TimeUnit;
4 import java.util.concurrent.atomic.AtomicInteger;
5 import java.util.stream.IntStream;
6
7 public class App {
8
9 public static void stop(ExecutorService executor) {
10 try {
11 executor.shutdown();
12 // give it time to finish
13 executor.awaitTermination(60, TimeUnit.SECONDS);
14 } catch (InterruptedException ex) {
15 ex.printStackTrace();
16 } finally {
17 if (!executor.isTerminated()) {
18 System.out.println("Termination interrupted");
19 }
20 executor.shutdown();
21 }
22 }
23
24 public static void main(String[] args) throws InterruptedException {
25 int numProcessors = Runtime.getRuntime().availableProcessors();
26 ExecutorService executor = Executors
27 .newFixedThreadPool(numProcessors);
28 AtomicInteger atomicInt = new AtomicInteger(0);
29
30 IntStream.range(0, 10000)
31 .forEach(i -> executor.submit(atomicInt::incrementAndGet));
32
33 stop(executor);
34 System.out.println("Value is: " + atomicInt.get());
35 }
36 }

Code 8.1: Example of usage for AtomicInteger.

9 Synchronizers
Five classes aid common special purpose synchronization idioms:

Semaphore This class is capable of maintaining whole set of permits. Useful in scenarios where
we need to limit the amount of concurrent access to certain parts of the application. In
the next example, we define a semaphore limiting the number of concurrent accesses to
the total of available processors divided by 2 and trying execute all tasks (the number the
tasks is the number of available processors). The semaphore limits the concurrent access
only to the number defined previously. Each task will try acquire the semaphore (trying
by one second) and if it got it, it will sleep for 5 seconds. In other case, it will print that
could not acquire the semaphore.

1 import java.util.concurrent.Executors;
2 import java.util.concurrent.ExecutorService;
3 import java.util.concurrent.TimeUnit;
4 import java.util.concurrent.Semaphore;

16
5 import java.util.concurrent.atomic.AtomicInteger;
6 import java.util.stream.IntStream;
7
8 public class App {
9 public static void main(String[] args) throws InterruptedException {
10 int numProcessors = Runtime.getRuntime().availableProcessors();
11 ExecutorService executor = Executors
12 .newFixedThreadPool(numProcessors);
13 Semaphore semaphore = new Semaphore(numProcessors / 2);
14 Runnable longRunningTask = () -> {
15 boolean permit = false;
16 try {
17 permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
18 if (permit) {
19 System.out.println("Semaphore acquired");
20 sleep(5);
21 } else {
22 System.out.println("Could not acquire semaphore");
23 }
24 }
25 catch (InterruptedException e) {
26 System.out.println("Error " + e.getMessage());
27 e.printStackTrace();
28 } finally {
29 if (permit) {
30 semaphore.release();
31 }
32 }
33 };
34 IntStream.range(0, numProcessors)
35 .forEach(i -> executor.submit(longRunningTask));
36 stop(executor);
37 }
38 public static void stop(ExecutorService executor) {
39 try {
40 executor.shutdown();
41 // give it time to finish
42 executor.awaitTermination(60, TimeUnit.SECONDS);
43 } catch (InterruptedException ex) {
44 ex.printStackTrace();
45 } finally {
46 if (!executor.isTerminated()) {
47 System.out.println("Termination interrupted");
48 }
49 executor.shutdown();
50 }
51 }
52 public static void sleep(int seconds) {
53 try {
54 TimeUnit.SECONDS.sleep(seconds);
55 } catch (InterruptedException e) {
56 throw new IllegalStateException(e);
57 }
58 }
59 }

Code 9.1: First example of synchronizers: Semaphore

CountDownLatch This class is a synchronization aid that allows one or more threads to wait
until a set of operations being performed in other threads completes. From the java docu-
mentation, we have:

17
A CountDownLatch is initialized with a given count. The await method blocks
until the current count reaches zero due to invocations of the countDown()
method, after which all waiting threads are released and any subsequent invo-
cations of await return immediately. This is a one-shot phenomenon - count
cannot be reset. If you need a version that resets the count, consider using a
CyclicBarrier.

1 import java.util.Collections;
2 import java.util.LinkedList;
3 import java.util.List;
4 import java.util.concurrent.CountDownLatch;
5 import java.util.concurrent.ExecutorService;
6 import java.util.concurrent.Executors;
7 import java.util.concurrent.TimeUnit;
8 import java.util.stream.IntStream;
9
10 public class App {
11
12 public static void main(String[] argv){
13 int numProcessors = Runtime.getRuntime().availableProcessors();
14 ExecutorService consumerExecutors = Executors.newFixedThreadPool(numProcessors);
15 List<Message> queue = Collections.synchronizedList(new LinkedList<Message>());
16 CountDownLatch doneSignal = new CountDownLatch(1);
17 CountDownLatch doneProducingSignal = new CountDownLatch(1);
18 CountDownLatch doneConsumingSignal = new CountDownLatch(numProcessors);
19 IntStream.range(0, numProcessors)
20 .forEach(i -> {
21 String name = String.format("%d", i);
22 consumerExecutors.execute(new Consumer(name, queue,
23 doneProducingSignal,
24 doneConsumingSignal));
25 });
26 queue.add(new Message( "1", 15000, doneSignal));
27 queue.add(new Message( "2", 15000, new CountDownLatch(1)));
28 doneProducingSignal.countDown();
29 boolean doneProcessing = false;
30 try {
31 doneProcessing = doneSignal.await(3, TimeUnit.SECONDS);
32 } catch (InterruptedException e1) {
33 e1.printStackTrace();
34 }
35 if ( doneProcessing ){
36 System.out.println( "Processing is done.");
37 } else {
38 System.out.println( "Processing is still running.");
39 }
40
41 System.out.println( "Shutting down the consumerExecutors");
42 doneProducingSignal.countDown();
43 try {
44 doneConsumingSignal.await();
45 } catch (InterruptedException e1) {
46 e1.printStackTrace();
47 }
48 consumerExecutors.shutdown();
49 System.out.println( "Done");
50 }
51 }
52
53 class Consumer implements Runnable {

18
54 private String id;
55 private List<Message> queue;
56 private CountDownLatch doneProducing;
57 private CountDownLatch doneConsuming;
58
59 Consumer(String id, List<Message> queue,
60 CountDownLatch doneProducing,
61 CountDownLatch doneConsuming){
62 this.id = id;
63 this.queue = queue;
64 this.doneProducing = doneProducing;
65 this.doneConsuming = doneConsuming;
66 }
67
68 @Override
69 public void run() {
70 while(doneProducing.getCount() != 0 || !queue.isEmpty()){
71 Message m = null;
72 synchronized(queue){
73 if(!queue.isEmpty()) m = queue.remove(0);
74 }
75 if(m != null) consume(m);
76 }
77 System.out.printf("Consumer %s done\n", id);
78 doneConsuming.countDown();
79 }
80 public void consume(Message m ){
81 System.out.printf("Consumer %s consuming message %s\n",
82 id, m.getId());
83 try {
84 Thread.sleep(m.getTime());
85 } catch (InterruptedException e) {
86 e.printStackTrace();
87 }
88 System.out.printf("Consumer %s done consumming msssage %s\n",
89 id, m.getId());
90 m.getLatch().countDown();
91 }
92 }
93
94 class Message {
95 private String id;
96 private int time;
97 private CountDownLatch latch;
98 Message(String id, int time, CountDownLatch latch){
99 this.id = id;
100 this.time = time;
101 this.latch = latch;
102 }
103 public String getId() {
104 return id;
105 }
106 public int getTime() {
107 return time;
108 }
109 public CountDownLatch getLatch() {
110 return latch;
111 }
112 }

Code 9.2: CountDownLatch example.

19
CyclicBarrier This class is a synchronization aid that allows a set of threads to all wait for
each other to reach a common barrier point. CyclicBarriers are useful in programs
involving a fixed sized party of threads that must occasionally wait for each other. The
barrier is called cyclic because it can reused after the waiting threads are released.
An example of use of this class is for using a barrier in a parallel decomposition design:
1 import java.text.SimpleDateFormat;
2 import java.util.Date;
3 import java.util.concurrent.BrokenBarrierException;
4 import java.util.concurrent.CyclicBarrier;
5 import java.util.concurrent.ExecutorService;
6 import java.util.concurrent.Executors;
7 import java.util.concurrent.TimeUnit;
8 import java.util.stream.IntStream;
9
10 public class App {
11 public static void main(String[] args) {
12 int numProcessors = Runtime.getRuntime().availableProcessors();
13 CyclicBarrier cyclicBarrier = new CyclicBarrier(numProcessors,
14 new CyclicTask());
15 ExecutorService executor = Executors.newFixedThreadPool(numProcessors);
16 System.out.println("Spawning Threads");
17 IntStream.range(0, numProcessors)
18 .forEach(i -> {
19 String name = String.format("Thread-%d", i);
20 executor.execute(new WorkerThread(cyclicBarrier, name));
21 });
22 System.out.println("Spawning Finished");
23 stop(executor);
24 }
25
26 public static void stop(ExecutorService executor) {
27 try {
28 executor.shutdown();
29 // give it time to finish
30 executor.awaitTermination(60, TimeUnit.SECONDS);
31 } catch (InterruptedException ex) {
32 ex.printStackTrace();
33 } finally {
34 if (!executor.isTerminated()) {
35 System.out.println("Termination interrupted");
36 }
37 executor.shutdown();
38 }
39 }
40 }
41
42 class WorkerThread implements Runnable {
43 private CyclicBarrier cyclicBarrier;
44 private String name;
45
46 public WorkerThread(CyclicBarrier cyclicBarrier, String name) {
47 this.name = name;
48 this.cyclicBarrier = cyclicBarrier;
49 }
50
51 public void run() {
52 try {
53 SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
54 System.out.printf("%s: Doing Step 1 Work on %s\n",
55 getFormattedDate(sdf), name);

20
56 sleep(getRandomWaitTime());
57 System.out.printf("%s: Doing Step 1 more work on %s\n",
58 getFormattedDate(sdf), name);
59 sleep(getRandomWaitTime());
60 System.out.printf("%s: Finished Step 1 work on %s\n",
61 getFormattedDate(sdf), name);
62 // Await returns for the other threads
63 int count = cyclicBarrier.await();
64 System.out.printf("%s: Cyclic Barrier count on %s is %d\n",
65 getFormattedDate(sdf), name, count);
66 // If all threads have arrived 2 lines above, reset the barrier
67 if(count == 0) {
68 cyclicBarrier.reset();
69 }
70 System.out.printf("%s: Doing Step 2 Batch of Work on %s\n",
71 getFormattedDate(sdf), name);
72 sleep(getRandomWaitTime());
73 System.out.printf("%s: Doing Some more Step 2 Batch of work on %s\n",
74 getFormattedDate(sdf), name);
75 sleep(getRandomWaitTime());
76 System.out.printf("%s: Finished Step 2 Batch of work on %s\n",
77 getFormattedDate(sdf), name);
78 count = cyclicBarrier.await();
79 String template = "%s: Cyclic Barrier count end of " +
80 "Step 2 Batch of work on %s is %d\n";
81 System.out.printf(template, getFormattedDate(sdf), name, count);
82 } catch(InterruptedException | BrokenBarrierException e) {
83 e.printStackTrace();
84 }
85 }
86 public static void sleep(int milliseconds) {
87 try {
88 TimeUnit.MILLISECONDS.sleep(milliseconds);
89 } catch (InterruptedException e) {
90 throw new IllegalStateException(e);
91 }
92 }
93 private String getFormattedDate(SimpleDateFormat sdf) {
94 return sdf.format(new Date());
95 }
96 private int getRandomWaitTime() {
97 return (int) ((Math.random() + 1) * 1000);
98 }
99 }
100 class CyclicTask implements Runnable {
101 private int count = 1;
102
103 @Override
104 public void run() {
105 System.out.printf("Cyclic Barrier Finished %d\n", count++);
106 }
107 }

Code 9.3: CyclicBarrier example

Phaser This class is similar to CyclicBarrier and CountDownLatch in functionality but


supporting more flexible usage. This class allows register tasks at any time and it permits
un-registering also. Like a CyclicBarrier, a Phaser may be repeatedly awaited for
tasks synchronization over the distinct phases. Method arriveAndAwaitAdvance() has
effect analogous to CyclicBarrier.await. With this tool also is possible monitoring

21
its state and determine if it terminate. Let’s see an example:

1 import java.text.SimpleDateFormat;
2 import java.util.Date;
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5 import java.util.concurrent.TimeUnit;
6 import java.util.concurrent.Phaser;
7 import java.util.stream.IntStream;
8
9 public class App {
10 public static void main(String[] args) {
11 int numProcessors = Runtime.getRuntime().availableProcessors();
12 Phaser phaser = new Phaser();
13 ExecutorService executor = Executors.newFixedThreadPool(numProcessors);
14 System.out.println("Spawning Threads");
15 phaser.register(); // registering main thread
16 IntStream.range(0, numProcessors)
17 .forEach(i -> {
18 String name = String.format("Thread-%d", i);
19 executor.execute(new WorkerThread(phaser, name));
20 });
21 System.out.println("Spawning Finished");
22 phaser.arriveAndDeregister();
23 stop(executor);
24 }
25 public static void stop(ExecutorService executor) {
26 try {
27 executor.shutdown();
28 // give it time to finish
29 executor.awaitTermination(60, TimeUnit.SECONDS);
30 } catch (InterruptedException ex) {
31 ex.printStackTrace();
32 } finally {
33 if (!executor.isTerminated()) {
34 System.out.println("Termination interrupted");
35 }
36 executor.shutdown();
37 }
38 }
39 }
40 class WorkerThread implements Runnable {
41 private Phaser phaser;
42 private String name;
43 public WorkerThread(Phaser phaser, String name) {
44 this.name = name;
45 this.phaser = phaser;
46 this.phaser.register();
47 }
48 public void run() {
49 SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
50 System.out.printf("%s:[%s] Doing Step 1 Work\n",
51 getFormattedDate(sdf), name);
52 sleep(getRandomWaitTime());
53 System.out.printf("%s:[%s] Doing Step 1 more work\n",
54 getFormattedDate(sdf), name);
55 sleep(getRandomWaitTime());
56 System.out.printf("%s:[%s] Finished Step 1 work\n",
57 getFormattedDate(sdf), name);
58 phaser.arriveAndAwaitAdvance();
59 System.out.printf("%s:[%s] Past the barrier.\n",

22
60 getFormattedDate(sdf), name);
61 int phase = phaser.getPhase();
62 // here we had a reset with CyclicBarrier
63 System.out.printf("%s:[%s] Phaser count on %d\n",
64 getFormattedDate(sdf), name, phase);
65 System.out.printf("%s:[%s] Doing Step 2 Batch of Work\n",
66 getFormattedDate(sdf), name);
67 sleep(getRandomWaitTime());
68 System.out.printf("%s:[%s] Doing Some more Step 2 Batch of work\n",
69 getFormattedDate(sdf), name);
70 sleep(getRandomWaitTime());
71 System.out.printf("%s:[%s] Finished Step 2 Batch of work\n",
72 getFormattedDate(sdf), name);
73 phaser.arriveAndAwaitAdvance();
74 phase = phaser.getPhase();
75 System.out.printf("%s:[%s] Phaser finish on: %d\n",
76 getFormattedDate(sdf), name, phase);
77 }
78 public static void sleep(int milliseconds) {
79 try {
80 TimeUnit.MILLISECONDS.sleep(milliseconds);
81 } catch (InterruptedException e) {
82 throw new IllegalStateException(e);
83 }
84 }
85 private String getFormattedDate(SimpleDateFormat sdf) {
86 return sdf.format(new Date());
87 }
88 private int getRandomWaitTime() {
89 return (int) ((Math.random() + 1) * 1000);
90 }
91 }

Code 9.4: Phaser example

Exchanger A synchronization point at which threads can pair and swap elements within pairs.
Each thread presents some object on entry to the exchange method, matches with a part-
ner thread, and receives its partner’s object on return. An Exchanger may be viewed as a
bidirectional form of a SynchronousQueue. Exchangers may be useful in applications
such as genetic algorithms and pipeline designs.
Just to finish this section, an interesting comment taken from stackoverflow about real-life
examples for use CountDownLatch, Semaphores, Mutex and CyclicBarriers is the following:
In a hypothetical theater:
• It is called Mutex if only one person is allowed to watch the play.
• It is called Semaphore if N number of people are allowed to watch the play. If
anybody leaves the Theater during the play then other person can be allowed
to watch the play.
• It is called CountDownLatch if no one is allowed to enter until every person
vacates the theater. Here each person has free will to leave the theater.
• It is called CyclicBarrier if the play will not start until every person enters
the theater. Here a showman can not start the show until all the persons enter
and grab the seat. Once the play is finished the same barrier will be applied for
next show.
Here, a person is a thread, a play is a resource.

23
10 VarHandle
VarHandle (java.lang.invoke.VarHandle) is a class that provides low-level mechanisms
and APIs for method invocations and memory operations. The goal of this class is replace the
operations in sun.misc.Unsafe, used in Java version less than 9 but with a safe equivalent.
It provides access to variables under various access modes, including plain read/write access,
volatile read/write access and compare-and-set. Additionally, it provides a set of static methods
referred to as memory fence methods for fine-grained control of memory ordering. The specifi-
cation of language permits other threads observe operations as if they were executed in orders
different than are apparent in program source code, subject to constraint arising. The methods
fullFence, acquireFence, releaseFence, loadLoadFence and storeStoreFence,
can be used to impose constraints.

11 References
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
lang/invoke/VarHandle.html
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/CyclicBarrier.html
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/Executors.html
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/ExecutorService.html
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/Executors.html
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/Phaser.html
• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/Semaphore.html

• https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/
util/concurrent/CountDownLatch.html
• https://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-
• https://winterbe.com/posts/2015/04/30/java8-concurrency-tutorial-synchronized-loc

• https://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurren
• https://stackoverflow.com/questions/10156191/real-life-examples-for-countdownlatc
32416323#32416323
• http://oliviertech.com/es/java/CountDownLatch-java-example/

24

You might also like