Skip to content

Concurrent and parallel programming

Introduction

Concurrent programming involves designing and creating programs that, in the execution phase, consist of at least two concurrent units (each of them being a sequential process), along with ensuring synchronization between them. These entities can be threads or processes.

Thread and process

When we launch our applications, we start a new process at the operating system level. We can get a list of such processes using the ps command. Multiple threads can exist within one process. Threads have a common address space and open system structures (such as open files), processes in turn have independent address spaces.

Thread vs process

NOTE: The command ulimit -a will tell us the currently configured resource limits for the operating system.

Thread

Thread is the execution thread in the program. The Java Virtual Machine allows an application to run multiple threads simultaneously. Each thread has a priority. Higher priority threads run before lower priority threads.

When the JVM starts, normally the main thread which calls the main method is executed. Then we can start other threads from the main thread. These threads run until one of the following occurs:

  • the threads will finish their work
  • thread will throw an exception
  • the method System.exit () (from any thread) will be called.

Thread Java

A Java thread is represented by the Thread class. We can create threads in many ways, e.g .:

  • extending the Thread class and overriding therun method
  • by implementing the functional interface Runnable.

Inheriting from the Thread class

If you want to define a new thread and decide to extend the Thread class, all code that should execute in a separate thread must go to therun method. The example shows creating a new thread by extending the Thread class. Additionally, a thread is started using the start () method. The main thread and the thread created by us, at the end of their operation, print their identifier with Thread.currentThread().GetId():

public class ThreadsExample {
  public static void main(String[] args) {
    new HelloWorldThread().start();
    System.out.println(Thread.currentThread().getId());
  }
}

class HelloWorldThread extends Thread {
  @Override
  public void run() {
    System.out.println("Hello World from another Thread");
    System.out.println(Thread.currentThread().getId());
  }
}

A sample output of the program may look like this:

1
Hello World from another Thread
14

NOTE: Inheriting from Thread is NOT RECOMMENDED. If you want to create a new thread, let's use the Runnable interface.

Runnable

Another, better way to create a thread is to declare a class that implements the Runnable interface with one abstract, argumentlessrun method. A Runnable instance may be passed as an argument to theThread class constructor. We start the created thread using the start method.

The next example runs two separate threads using the Runnable interface, one by defining a separate class, the second time by using [lambda] (functional_programming.md # lambda-expressions):

public class ThreadsExample {
  public static void main(String[] args) {
    new Thread(new HelloWorldRunnableThread()).start();
    new Thread(() -> System.out.println("Hello from another thread implemented with lambda")).start();
  }
}

class HelloWorldRunnableThread implements Runnable {
  @Override
  public void run() {
    System.out.println("Hello World from another Thread");
  }
}

Breaking a thread

Depending on the circumstances and the state of the application, we may occasionally want to interrupt the work being performed by a certain thread. However, it is impossible to break the thread in a trivial way. The Thread class provides astop method, but we shouldn't use it. At most, we can send a request to stop such a thread. The programmer, in the code of a separate thread, decides what to do with this fact. We send such a signal by calling the interrupt method, available on theThread class instance. Depending on the state of the thread, there are two possible situations:

  • the thread throws an InterruptedException exception based on the signal
  • the thread can check if it has received a signal (stop request) with the method:

    • isInterrupted, where when called, the stop request not information is deleted
    • interrupted, which, in addition to information whether a stop signal has been sent, also resets the status.

The following examples show how we can use these methods:

public class ThreadsExample {
  public static void main(String[] args) {
    final Thread sleepingThread = new Thread(new SleepingThread());
    sleepingThread.start();
    sleepingThread.interrupt(); // sending a stop request
  }
}

class SleepingThread implements Runnable {

  @Override
  public void run() {
    System.out.println("I will go to sleep");
    try {
      Thread.sleep(3000L);
    } catch (InterruptedException e) { // catching an InterruptedException if an interrupt signal was sent while the sleep method is executing
      System.out.println("I was interrupted during sleep");
    }
    System.out.println("I am exiting");
  }
}

The possible (and most likely) output of the program above is:

I will go to sleep
I was interrupted during sleep
I am exiting

The next example creates a separate thread that checks during its execution whether a stop request has been sent in the meantime:

public class ThreadsExample {
  public static void main(String[] args) {
    final Thread sleepingThread = new Thread(new SleepingThread());
    sleepingThread.start();
    sleepingThread.interrupt();
  }
}

class SleepingThread implements Runnable {

  @Override
  public void run() {
    final List<Integer> ints = new ArrayList<>();
    for (int idx = 0; idx < 1000; idx++) {
      ints.add(new Random().nextInt());
    }
    if (Thread.currentThread().isInterrupted()) { // or resetting the status of Thread.interrupted()
      System.out.println("I was interrupted...");
      return;
    }
    final int sum = ints.stream().mapToInt(value -> value).sum();
    System.out.println("Sum is " + sum);
  }
}

The example above will print the sum of the generated numbers to the screen, only if the interrupt () method is called after the signal is checked by the thread represented by the SleepingThread class.

Synchronization

While creating a multi-threaded application, we must remember that in such an application:

  • there is one heap, regardless of the number of threads
  • each running thread creates a separate stack (stack)

Therefore, in a multithreaded application, we must take into account the fact that an object on the heap at one time can be changed by many threads. In order to avoid this, i.e. the object can be accessed only in a single thread at the same time, we can use the synchronization mechanism.

Java introduces two basic ways to synchronize:

  • method synchronization
  • code block synchronization

Both of the above methods are implemented using the synchronized keyword.

The synchronization problem can be seen in the following code snippet. We create two threads in it that modify * the same * instance of the Pair class. At the end of each thread, we list the final values ​​of the left andright fields to the screen. Even though we started the program with the values ​​0 and0, respectively, and both threads incremented them 100 times, we most likely will not get the value 200 for these fields.

public class ThreadsExample {
  public static void main(String[] args) {
    final Pair pair = new Pair(0, 0);
    new Thread(new DummyPairIncrementer(pair)).start();
    new Thread(new DummyPairIncrementer(pair)).start();
  }
}

class Pair {
  private Integer left;
  private Integer right;

  public Pair(final Integer left, final Integer right) {
    this.left = left;
    this.right = right;
  }

  public void incrementLeft() {
    left++;
  }

  public void incrementRight() {
    right++;
  }

  public Integer getLeft() {
    return left;
  }

  public Integer getRight() {
    return right;
  }
}

class DummyPairIncrementer implements Runnable {
  private final Pair pair;

  public DummyPairIncrementer(final Pair pair) {
    this.pair = pair;
  }

  @Override
  public void run() {
    for (int idx = 0; idx < 100; idx++) {
      pair.incrementLeft();
      pair.incrementRight();
    }
    System.out.println(pair.getLeft() + " " + pair.getRight());
  }
}

The problem described in the example is that increment is one instruction in the context of the code we are writing, but to the processor it is actually * three * instructions. Those are:

  • (1) retrieving the current value from the memory
  • (2) adding one to the downloaded value
  • (3) save increased value to memory.

If one thread performs the operation (1), but does not yet perform the operation (3), and during this time the other thread manages to perform the operation (1), the two increments will result in increasing the value by 1, not by 2.

Synchronization of the method

In order to synchronize the method, we add the keyword synchronized to its declaration. When a method is synchronized, the calling thread has exclusive access to it until it completes. To correct the problem in the previous example, we need to synchronize the following methods:

  public synchronized void incrementLeft() {
    left++;
  }

  public synchronized void incrementRight() {
    right++;
  }
Thanks to this solution, the programmer is able to control access to the object, making it more predictable and sequential across multiple threads.

Block synchronization

Block synchronization performs exactly the same mechanism as method synchronization, but it can reduce the scope of data synchronization only to individual instructions related to, e.g., a class field. In order to implement block synchronization, the synchronized keyword is also used, but additionally between()we insert the object that we want to access in a concurrency in a sequential manner.

If we decided to use code block synchronization in the synchronized methods from the previous example, we could change their implementation, e.g. to:

  public void incrementLeft() {
    System.out.println("Out of synchronized block");
    synchronized (this) {
      left++;
      System.out.println("In synchronized block");
    }
    System.out.println("Out of synchronized block");
  }

  public void incrementRight() {
    System.out.println("Out of synchronized block");
    synchronized (this) {
      right++;
      System.out.println("In synchronized block");
    }
    System.out.println("Out of synchronized block");
  }

NOTE: We should perform synchronization on the final objects/fields.

Join

We often use additional threads in applications to calculate certain data, which we then process, e.g. in the main thread. Before we can start processing, we are forced to wait for all threads enumerating data to finish. To wait for the thread to end, we need to use the join method. Overloads are available:

  • no argument, waiting for the thread to exit
  • versions with arguments, where we can give the number of milliseconds (and optionally nanoseconds), meaning the maximum waiting time for the thread to terminate.

Another example shows how we can use the join method:

public class ThreadsExample {
  public static void main(String[] args) throws InterruptedException {
    final List<Integer> ints = new ArrayList<>();
    final Thread threadA = new Thread(new SimpleThread(ints));
    final Thread threadB = new Thread(new SimpleThread(ints));

    threadA.start();
    threadB.start();

    threadA.join(1000L);
    threadB.join(1000L);;
    System.out.println(ints.size());
  }
}

class SimpleThread implements Runnable {

  private final List<Integer> ints;

  SimpleThread(final List<Integer> ints) {
    this.ints = ints;
  }

  @Override
  public void run() {
    synchronized (this.ints) {
      ints.add(new Random().nextInt());
    }
  }
}

Deadlock

In a situation where several threads block each other indefinitely, this is called a deadlock. The program is unable to complete the indicated operation due to the permanent mutual locking of resources. It can be described as follows:

  • A is waiting for B because:
  • B waits for A.

Deadlock is presented in the example below:

public class DeadLockExample {
  public static void main(String[] args) throws InterruptedException {
    final String r1 = "r1";
    final String r2 = "r2";

    Thread t1 = new Thread() {
      public void run() {
        synchronized (r1) {
          System.out.println("Thread 1: Locked r1");
          try {
            Thread.sleep(100);
          } catch (InterruptedException ignored) {
          }
          synchronized (r2) {
            System.out.println("Thread 1: Locked r2");
          }
        }
      }
    };
    Thread t2 = new Thread() {
      public void run() {
        synchronized (r2) {
          System.out.println("Thread 2: Locked r1");
          try {
            Thread.sleep(100);
          } catch (InterruptedException ignored) {
          }
          synchronized (r1) {
            System.out.println("Thread 2: Locked r2");
          }
        }
      }
    };

    t1.start();
    t2.start();

    t1.join();
    t2.join();
    System.out.println("Exiting? No I will never reach this line of code because threads will NOT join");
  }

}
In the above case:

  • The thread t1 is blocking access to the resourcer1, then tries to force access to the resource r2.

  • The thread t2 blocks access to the resourcer2, then tries to access the resource r1.

Since neither thread t1 frees access to resourcer1, nor does thread t2 frees access tor2, this causes a so-called Deadlock.

Thread coordination

The synchronized keyword is used to prevent unwanted interaction of threads. However, it is not a sufficient measure to ensure that the threads work together. Often times, there may be a need not to perform a specific operation until a certain condition is met.

Queue<Runnable> runnableQueue = new LinkedList<>();
while (consumerQueue.isEmpty()) {
  // waiting, waiting and still waiting for something to appear
}
actionQueue.poll().run();

The above code fragment addresses the presented problem, but it is also very ineffective, because it executes continuously while waiting. The same problem can be solved with the wait andnotify/notifyAll methods.

wait - notify

wait

The thread calls the wait method on the given object when it expects something to happen (usually in the context of that object), e.g. an object state change to be performed by another thread and which is implemented e.g. by changing the value of some variable - object fields). Calling the wait method blocks the thread, and the method on which the operation is being called must be synchronized. Another thread can change the state of the object and notify the waiting thread about it (using the notify or notifyAll method).

Queue<Runnable> runnableQueue = new LinkedList<>();
while (runnableQueue.isEmpty()) {
  try {
    wait();
  } catch (InterruptedException e) {
    System.err.println("Oops");
  }
}
runnableQueue.poll().run();

notify and notifyAll

The object is unblocked when another thread calls the notify ornotifyAll method for the same object where the thread is waiting:

  • Calling notify unblocks one of the waiting threads, which can be any of them.
  • The notifyAll method unblocks all threads waiting on the object.
  • The call to notify ornotifyAll must be in a synchronized block / method.

Thread coordination diagram

public class ThreadsExample {
  public static void main(String[] args) throws InterruptedException {
    final Customer customer = new Customer();
    final Thread withDrawThread = new Thread(new WithdrawThread(customer));
    final Thread depositThreadA = new Thread(new DepositThread(customer));
    final Thread depositThreadB = new Thread(new DepositThread(customer));

    withDrawThread.start();
    depositThreadA.start();
    depositThreadB.start();
  }
}

class Customer {
  private int availableAmount = 0;

  synchronized void withdraw(int amountToWithdraw) {
    System.out.println("Trying to withdraw " + amountToWithdraw + " PLN");
    while (availableAmount < amountToWithdraw) {
      System.out.println("Not enough money! Waiting for transfer!");
      try {
        wait();
      } catch (InterruptedException e) {
        System.err.println("Oops");
      }
    }
    System.out.println("Withdraw successful!");
  }

  synchronized void deposit(final int amountToDeposit) {
    System.out.println("Depositing " + amountToDeposit + " PLN");
    availableAmount += amountToDeposit;
    notify();
  }
}

class WithdrawThread implements Runnable {

  private final Customer customer;

  WithdrawThread(final Customer customer) {
    this.customer = customer;
  }


  @Override
  public void run() {
    customer.withdraw(1000);
  }
}

class DepositThread implements Runnable {
  private final Customer customer;

  DepositThread(final Customer customer) {
    this.customer = customer;
  }

  @Override
  public void run() {
    customer.deposit(500);
  }
}

In the example above, the WithdrawThread thread is paused (wait) until you have sufficient funds in your account. Each payment will trigger the thread (notify). The thread WithdrawThread will not finish running until the givenCustomer has the required funds.

Callable and Future

A generic function interface representing a task that can return either a result or an exception with the argumentless call () method. The Callable interface is similar to the Runnable interface, except that therun method cannot return any result. Both interfaces are similar to each other due to their potential use in multithreaded service.

public class GetRequest implements Callable<String> {

    @Override
    public String call() throws Exception {
        return "Dummy http response";
    }
}

The Future is the interface that represents the future result of the async method, which will eventually be returned in the future after the operation has finished processing. The operation operation value can be retrieved using the get () method, which works similarly to the join method in theThread class, ie it blocks the current thread and waits for the expected result to be available.

ExecutorService

When creating multi-threaded applications, we rarely use a low-level API and manage threads manually. Whenever possible, we should use the so-called thread pool, which is a group of threads managed by an external entity. One of such mechanisms in Java is the ExecutorService interface, which simplifies the execution of tasks in asynchronous mode, using a pool of threads for this. To create an ExecutorService instance, we can use a factory, theExecutors class, which has some useful static methods. The basic ones are:

  • newSingleThreadExecutor() - returns ExecutorService running on one thread
  • newFixedThreadPool(int nThreads) - returns ExecutorService running on the thread pool of the given size.

In addition, we also have:

  • newCachedThreadPool() - creates an ExecutorService, which in the absence of a thread could handle a new task, adds a new thread to the pool. Additionally, threads are removed from the pool if it does not get a new task to be performed for one minute.
  • newScheduledThreadPool(int corePoolSize) - creates an ExecutorService that starts the task after a certain time or at specified intervals.

The code below shows the different ways to create different ExecutorService instances:

public class ExecutorsCreationExample {
  public static void main(String[] args) throws InterruptedException {
    final int cpus = Runtime.getRuntime().availableProcessors();
    final ExecutorService singleThreadES = Executors.newSingleThreadExecutor(); // single thread pool
    final ExecutorService executorService = Executors.newFixedThreadPool(cpus); // pool with threads equal to cpu
    final ExecutorService cachedES = Executors.newCachedThreadPool();           // cached thread pool
    final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(cpus); // scheduled thread pool with cpu equal number of threads
  }
}

Closing the ExecutorService

When creating an ExecutorService, we must remember to manually close it. The following methods are used for this:

  • shutdown() - the thread pool will stop accepting new tasks, those started will be completed, and then the pool will be closed
  • shutdownNow() - Similar to shutdown,ExecutorService will stop accepting new tasks, in addition it tries to stop all active tasks, stops processing pending tasks and returns a list of tasks waiting for execution.

Performing tasks

In order to perform a task on a thread from the pool, we can use the following methods:

  • submit() - performs a Callable orRunnable task, e.g .:
public class CallableFutureExample {
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newSingleThreadExecutor(); // creating an ExecutorService with a single-threaded pool
    Future<String> result = executorService.submit(() -> "I am result of callable!"); // Callable implementation using lambda
    try {
      System.out.println("Prinint result of the future: " + result.get());
    } catch (InterruptedException | ExecutionException e) {
      System.err.println("Oops");
    }
    executorService.shutdown(); // remember to close the ExecutorService manually
  }
}
  • invokeAny() - ExecutorService in its thread pool starts executing the list of input jobs. Returns the result of tasks that were started that were successfully completed when the first completed successfully. The remaining unfinished tasks will be canceled.

This behavior is illustrated by another example:

public class HomeTasks {
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    List<Callable<String>> tasks = Arrays.asList(
        () -> {
          System.out.println("Thread: " + Thread.currentThread().getName());
          System.out.println("I'm shopping");
          Thread.sleep(5000);
          System.out.println("Thread: " + Thread.currentThread().getName() + ". Shopping done!");
          return "Shopping done!";
        },
        () -> {
          System.out.println("Thread: " + Thread.currentThread().getName());
          System.out.println("Washing dishes");
          Thread.sleep(2000);
          System.out.println("Thread: " + Thread.currentThread().getName() + ". Dishes washed");
          return "dishes washed";
        },
        () -> {
          System.out.println("Thread: " + Thread.currentThread().getName());
          System.out.println("Cleaning the room");
          Thread.sleep(1000);
          System.out.println("Thread: " + Thread.currentThread().getName() + ". Room cleaned");
          return "Room cleaned";
        }
    );
    try {
      String firstResult = executorService.invokeAny(tasks);
      System.out.println("FIRST RESULT: " + firstResult);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    executorService.shutdown();
  }
}

Possible output from the previous example, e.g.:

Thread: pool-1-thread-2
Washing dishes
Thread: pool-1-thread-1
I'm shopping
Thread: pool-1-thread-2. Dishes washed
Thread: pool-1-thread-2
Cleaning the room
PIERWSZY WYNIK: Dishes washed

Note that 'Room cleaned' was not displayed on the screen as the result of the first completed task was returned faster. The first result is "washed dishes". This fact is due to the fact that our pool has fewer threads than the number of tasks we want to run. Hence, despite the fact that the third Callable sleeps the shortest, it will start executing only when one of the threads from the pool is free, i.e. it finishes performing an active task.

  • invokeAll - executes all of the Callable tasks and returns a result list of typeList <Future <T>>.

Let's adapt the code from the previous example to execute the invokeAll method instead ofinvokeAny:

public class HomeTasks {
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    List<Callable<String>> tasks = Arrays.asList(
        () -> {
          System.out.println("Thread: " + Thread.currentThread().getName());
          System.out.println("I'm shopping");
          Thread.sleep(5000);
          System.out.println("Thread: " + Thread.currentThread().getName() + ". Shopping done!");
          return "Shopping done!";
        },
        () -> {
          System.out.println("Thread: " + Thread.currentThread().getName());
          System.out.println("Washing dishes");
          Thread.sleep(2000);
          System.out.println("Thread: " + Thread.currentThread().getName() + ". Dishes washed");
          return "Dishes washed";
        },
        () -> {
          System.out.println("Thread: " + Thread.currentThread().getName());
          System.out.println("Cleaning the room");
          Thread.sleep(1000);
          System.out.println("Thread: " + Thread.currentThread().getName() + ". Room cleaned");
          return "Room cleaned";
        }
    );
    try {
      List<Future<String>> futures = executorService.invokeAll(tasks);
      for (Future<String> future : futures) {
        System.out.println(future.get());
      }
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    executorService.shutdown();
  }
}

Example output:

Thread: pool-1-thread-1
I'm shopping
Thread: pool-1-thread-2
Washing dishes
Thread: pool-1-thread-2. Dishes washed
Thread: pool-1-thread-2
Cleaning the room
Thread: pool-1-thread-2. Room cleaned
Thread: pool-1-thread-1. Shopping done!
Shopping done!
Dishes washed
Room cleaned

Note that all tasks will always be completed.

ScheduledExecutorService

This implementation of ExecutorService allows you to schedule an operation to run after a certain time or interval. Within the methods of this ExecutorService implementation, we can distinguish the following methods:

  • scheduleAtFixedRate
  • scheduleWithFixedDelay

Each of the above methods returns a special object: ScheduledFuture, which [inherits] (oop.md # inherit) from the behavior of theFuture class and, apart from being able to cancel the task, allows you to return the time remaining until the next operation is performed.

scheduleAtFixedRate

This method allows you to perform a given action with a delay, and then cyclically every certain period of time, e.g.

public class SchedulerExecutorDemo {
  public static void main(String[] args) throws InterruptedException {
    DateFormat df = new SimpleDateFormat("dd:MM:yy:HH:mm:ss");
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.scheduleAtFixedRate(() -> {
      System.out.println("Start coffee!: " + df.format(Calendar.getInstance().getTime()));
      try {
        Thread.sleep(5000);
        System.out.println("finish coffee!: " + df.format(Calendar.getInstance().getTime()));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }, 1, 6, TimeUnit.SECONDS);
    Thread.sleep(15000L);
    executorService.shutdown();
  }
}

For the example above, each new task execution will start one second after the preceding one completes, the tasks will stop running when we call the shutdown method.

scheduleWithFixedDelay

The scheduleWithFixedDelay method, similar to thescheduleAtFixedRate, allows you to perform a given action with a delay, and then cyclically at a certain period of time. The only difference is that the time interval is counted from the end of the preceding task, not the beginning.

public class ScheduledWithFixedDelayDemo {
  public static void main(String[] args) throws InterruptedException {
    DateFormat df = new SimpleDateFormat("dd:MM:yy:HH:mm:ss");
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.scheduleWithFixedDelay(() -> {
      System.out.println("Start coffee!: " + df.format(Calendar.getInstance().getTime()));
      try {
        Thread.sleep(5000);
        System.out.println("finish coffee!: " + df.format(Calendar.getInstance().getTime()));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }, 1, 6, TimeUnit.SECONDS);
    Thread.sleep(15000L);
    executorService.shutdown();
  }
}

For the above example, each new task execution will start 6 seconds after the previous one completes.

Atomic Variables

[Earlier] (# Synchronization) we discussed that an operation such as increment from the Java perspective is a single expression, but for the processor it is several operations. We say that an operation is atomic if, while it is being performed, another thread cannot read or change the values of the variables being changed. The java.util.concurrent.atomic package defines classes that handle atomic operations on single variables. The classes of the Atomics group provide a set of synchronized operations, and the objects themselves can be safely shared between multiple threads. The types discussed are, for example:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
public class AtomicsDemo {
  public static void main(String[] args) {
    final ExecutorService executorService = Executors.newFixedThreadPool(2);
    final AtomicInteger atomicInteger = new AtomicInteger(0);
    executorService.submit(new IncrementingThread(atomicInteger));
    executorService.submit(new IncrementingThread(atomicInteger));
    executorService.shutdown();
  }
}

class IncrementingThread implements Runnable {

  private final AtomicInteger value;

  IncrementingThread(final AtomicInteger value) {
    this.value = value;
  }

  @Override
  public void run() {
    for (int idx = 0; idx < 1000; idx++) {
      value.incrementAndGet();
    }
    System.out.println(value.get()); // the slower thread will always print 2000
  }
}

volatile

Simply put, by creating a variable in the program, it can be stored in the main program memory or for optimization in the processor's memory (so-called L2 Cache). In multithreaded applications, it is possible that the value of a variable stored in the processor memory is different than that stored in the main memory. The one in main memory may be an obsolete value, and the current value in the processor's memory is not available for some threads, so our application may not work as expected.

The problem described above can be solved by marking such a variable with the keyword volatile, which means that the value will always be stored only in the main memory of the application. Importantly, volatile does not guarantee the atomicity of the operation, ie it is a way of synchronization that produces no less than atomic variables or thesynchronized keyword.

Another example shows the use of the volatile keyword:

public class VolatileDemo {

  public static volatile boolean shouldStop = false;

  public static void main(String[] args) throws InterruptedException {
    final ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(new VolatileThread());
    while (!shouldStop) {
      Thread.sleep(100L);
      System.out.println("Waiting for signal to stop checking that volatile boolean");
    }
    executorService.shutdown();
  }
}

class VolatileThread implements Runnable {

  @Override
  public void run() {
    System.out.println("Starting some processing");
    try {
      Thread.sleep(3000L);
    } catch (InterruptedException e) {
      System.err.println("Oops");
    }
    System.out.println("Processing finished");
    VolatileDemo.shouldStop = true;
  }
}

NOTE: We can only use the volatile keyword when defining class variables.