Java Concurrency: Concurrency and Parallelism

Concurrency and parallelism are important concepts any software engineer should understand. We use them everyday and doing it wrong could bring more trouble than advantages.

In this post series, we are going to talk about concurrency and parallelism in Java, focusing on the basics, moving to the daily uses, race condition problems and solutions, and finally, reactive programming.

In this post, we are going to talk about multitasking, how it relates with our daily life in a form of concurrency and parallelism, and finally, we are going to show why software uses them and how Java handles those two concepts.

TRY IT YOURSELF: You can find the source code of this post here.

Java Concurrency Series

If you like this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

Multitasking: Concurrency and Parallelism

Multitasking is the ability to perform multiple tasks at the same time, also called as concurrent tasks.

The definition is pretty straightforward, but, there is a catch: the phrase at the same time.

To go deep on what at the same time means, let’s take an example in our daily life: driving a car. (If you haven’t had the opportunity of driving a car, it is probable that you did in a video game or something similar, the example applies).

Driving a car is hard, you need to take into account a lot of variables and balancing them to avoid mistakes. The following are some of the tasks we do when we are driving:

  • Accelerating with one foot.
  • Braking with one foot.
  • Checking the left and right mirror.
  • Checking the back mirror.
  • Checking the map if you are using one.
  • Talking with your copilot.
  • Changing the music.
  • and more.
Pilot and things he needs to be aware of

You might think: oohh I don’t think about those tasks when I am driving. Yes, you are right, you don’t think about them because with practice, our minds are capable of doing some of those tasks without paying a lot of attention.

So, here is a question: when we drive, are we multitasking? well, no. In reality, we don’t do all of those tasks at the same time, our mind switch between one task to another pretty, pretty fast, and with practice, you will think this is done automatically.

Well, let’s introduce the first concept: Concurrency. Concurrency is the ability to do multiple tasks and switching between them so fast, that we think we are doing those tasks at the same time. Of course the switching is a task by itself, and it consumes time, which means, as many tasks you have to switch, as most costly switching could be.

Now, let’s improve how we drive the car. Let’s assign some tasks to the copilot:

  • Accelerating with one foot. (PILOT)
  • Braking with one foot. (PILOT)
  • Checking the left and right mirror. (PILOT)
  • Checking the back mirror. (PILOT)
  • Checking the map if you are using one. (COPILOT)
  • Talking with your copilot. (PILOT)
  • Changing the music. (COPILOT)

In this new setup, we release the pilot of checking the map and changing the music, so, the copilot will be in charge of those tasks. The pilot still has a lot of tasks that need to handle, and she will need to switch (concurrent) between them, also, the copilot has multiple tasks and he needs to switch (concurrent) over them. But, as we have two humans, doing some tasks to accomplish the final goal of driving a car, we can say that the pilot and the copilot are working in parallel.

Now, we introduce the second concept: Parallelism. Parallelism is the ability to do multiple tasks truly at the same time.

Concurrency and Parallelism are two important concepts in software engineering, let’s see why.

NOTE: For the following sections, we are going to use concurrent word to talk about tasks running “at the same time”.

Concurrency and Parallelism in Software Engineering

Any programming language out there applies concurrency and/or parallelism, why? because one of the advantages of the computer era is we can do a lot of tasks on a computer concurrently, for instance, meanwhile I am writing this post, I am listening to music and chatting with a messaging software.

Who does this magic? The operative system. The operative system (OS) (windows, linux, macos, etc) is an abstraction that allows us to use the hardware resources available. One of those resources are the schedule time or, the running time. That schedule/run time is done by the microprocessors, at the end, they run the tasks we define.

For instance, when we use Windows, and open multiple programs (tasks), the OS schedule running time over the microprocessor for those processes, and switch between them if it is required, creating the sensation of working with all of them concurrently.

Now, an OS has two main concepts about concurrent tasks we need to discuss:

  • Process: A process is a task (program) running over a operative system. An operative system can have multiple process running at the same time.
  • Thread: A thread is a subtask running over a process (program). A process can have multiple threads running at the same time.

The following image shows how processes and threads work together with the operative system and hardware:

Process, threads and OS

There, we can see that each process has threads, those threads maps to kernel threads (threads handled by the OS), and finally, the OS knows how to schedule execution time of those threads over the hardware.

NOTE: Depending on the programming language, the kernel threads doesn’t need to map to process threads directly. More about this in following sections.

In this case, we have concurrency, but not parallelism, because we have only one microprocessor (for instance, one person handling all the tasks to drive a car), which means, the OS needs to switch schedule and run time between the kernel threads and the one microprocessor. Threading gives us the sensation of parallelism.

What about parallelism then? Two tasks are parallel if they are executed over different hardware (Two different people in the car handling the driving tasks). For instance, if you have two microprocessors in your machine and assign one task to one microprocessor and the other to the other microprocessor, those two tasks are running on parallel.

Now, let’s see an example of parallelism:

Real parallelism

There, the OS can schedule run time over two microprocessors. In this case, PROCESS 1 runs over the MICROPROCESSOR 1 and PROCESS 2 runs over the MICROPROCESSOR 2. This is the ideal scenario, however, we have always limit resources, and a lot of processes and threads, we cannot have a microprocessor or core by thread.

Now, let’s see how Java handles concurrency.

Concurrency in Java

To understand concurrency in Java, let’s map threads, processes and OS to the JVM using the following code:

public class Main {
  public static void main(String[] args) {
    System.out.println(
            "Thread name: " + Thread.currentThread()
                    .getName());
  }
}

Thread class is the unit of work on Java. It represents a unit of schedule/running/processing time. That code just prints the current thread name. After running this program, we got the following:

Run console

There, we see that the running thread is called main. The following image shows how this piece of code translates to the threads, processes and OS we talked about before:

main Thread in Java

We have a JVM process, running over an OS under a microprocessor. The JVM process creates a thread when the process starts. That thread is named the main thread. The main thread is mapped to one kernel thread, and the OS schedules running time for the kernel thread in the underline hardware.

It is important to notice that the relationship between the Java thread and the kernel thread is one to one, which means, by any Java thread, I have a kernel thread. This concept is critical, as we don’t have unlimited kernel threads, which means, we don’t have unlimited Java threads. This is one of the major critics about Java and how it handles the threads.

NOTE: We will see how Project Loom and Reactive Programming address that issue in following posts.

Creating Threads in Java

There are two ways to create a thread in Java: extending the Thread class, or implementing the Runnable interface. Let’s start with the first one:

public class ThreadInheritance extends Thread {
  @Override
  public void run() {
    System.out.println(
            "This is a thread with inheritance: " + Thread
                    .currentThread()
                    .getName());
  }
}

We created a new class named ThreadInheritance, which extends from Thread. We must override the run() method. The Runnable strategy is pretty similar, as we can see in the following code:

public class ThreadRunnable implements Runnable {
  @Override
  public void run() {
    System.out.println(
            "This is a thread with runnable: " + Thread
                    .currentThread()
                    .getName());
  }
}

As we see, we created a class named ThreadRunnable, which implements Runnable interface. We override the run() method as we did before.

Now, let’s run some threads:

public class Main {
  public static void main(String[] args) {
    System.out.println(
            "Thread name: " + Thread.currentThread()
                    .getName());

    ThreadInheritance threadInheritance = new ThreadInheritance();
    threadInheritance.start();

    ThreadRunnable runnable = new ThreadRunnable();
    Thread threadRunnable = new Thread(runnable);
    threadRunnable.start();
  }
}

First, we create a ThreadInheritance object and started it. This will trigger the run() method we override. After, we created a ThreadRunnable object, a Thread object using that Runnable, and started the thread, again, this will trigger the run() method we override on the class ThreadRunnable.

The following is the output of that code:

Two threads running

We can see that three threads run, the main, Thread-1 and Thread-0. All three run concurrently.

Now, let’s split a tasks in multiple subtasks and run them concurrently.

Splitting a Task

To understand how a task can be split, let’s start with the definition of syncronous and asyncronous:

  • Synchronous: A client requests a task to be execute and wait for the result.
  • Asynchronous: A client requests a task to be execute and don’t care about the response, or, the response could be grabbed later.

Now, let’s split the driving task over two subtasks: checking the back mirror and accelerating. First, CheckBackMirror class.

public class CheckBackMirror implements Runnable {
  @Override
  public void run() {
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
    }
  }
}

And Accelarating class:

public class Accelerating implements Runnable {
  @Override
  public void run() {
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
    }
  }
}

We simulated the behavior, in this case, checking the back mirror takes 3 seconds, and accelerating takes 5 seconds. Now, let’s run those two tasks synchronously:

public class DrivingCar {
  public static void main(String[] args) {
    long startTime = System.currentTimeMillis();

    CheckBackMirror checkBackMirror = new CheckBackMirror();
    Accelerating accelerating = new Accelerating();

    checkBackMirror.run();
    accelerating.run();

    long endTime = System.currentTimeMillis();

    System.out.println(((endTime - startTime) / 1000) + " seconds");
  }
}

As we can see, the DrivingCar class (the client) calls explicitly the run() method, that means, we are not creating a thread. This process is synchronous, only after checkBackMirror.run() finishes, accelerating.run() continues. That shows that the DrivingCar class waits for the response of each of the tasks before continuing. At the end of the execution, we measure the time and the following is the result:

Synchronous run

The result is obvious, if we run a task that takes 5 seconds, and later, we run a task that takes 3 seconds, sequentially, the total time of execution will be 8 seconds. The following diagram illustrates what just happened:

Synchronous execution

Now, let’s run the same tasks asyncronously:

public class DrivingCar {
  public static void main(String[] args)
          throws InterruptedException {
    long startTime = System.currentTimeMillis();

    CheckBackMirror checkBackMirror = new CheckBackMirror();
    Thread checkBackMirrorThread = new Thread(checkBackMirror);
            
    Accelerating accelerating = new Accelerating();
    Thread acceleratingThread = new Thread(accelerating);

    checkBackMirrorThread.start();
    acceleratingThread.start();
    
    checkBackMirrorThread.join();
    acceleratingThread.join();

    long endTime = System.currentTimeMillis();

    System.out.println(((endTime - startTime) / 1000) + " seconds");
  }
}

In this case, we created two threads, one for each tasks, started them and join them to the main thread. Join to the main thread means that the main thread is going to be blocked in that line until the thread finishes, so, the main thread will finish when the checkBackMirrorThread and acceleratingThread finish. However, checkBackMirrorThread and acceleratingThread are not running sequentially, they run asynchronously. The following is the execution time:

Asynchronous run

This is interesting, we split a task in two, and run them asynchronous, which means, they run concurrently. As the main thread waits for both tasks to finish asynchronously, the total execution time is equal to the longer task to finish, in this case, accelerating. We saved 3 seconds. The following diagram illustrates what happens:

Asynchronous execution

Threading helps to execute tasks asynchronously and save time.

Final Thought

We just saw the basics of concurrent programming in Java, talking about concurrency and parallelism, and using the Thread class to create asynchronous tasks.

Concurrency in software engineering in one of the bases concept any developer should understand. Thinking concurrently is not an easy task, less fun to debug and to design, but, we have a lot of open sources framework which helps us to tackle those challenges.

In the next post, we are going to talk about some of those frameworks, like thread pools, futures and the Project Loom.

If you like this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s