Parallelism and Concurrency in Java

Parallelism and Concurrency in Java #

Computers can do many things at the same time. Operating systems run multiple programs simultaneously, so we can listen to music while browsing the web. A text editing program might search through one file while we edit another. Computers can also do the same thing on many processors. Image manipulation software often uses multiple cores when applying costly operations to large images.

Parallelism and concurrency are related but separate concepts. Parallelism is about how a program is executed, concurrency is about how a program is written. In traditional programming languages like Java, that support statements mutating memory explicitly, how a program is written is often close to how it is executed. It is still useful to distinguish between these concepts.

Using parallelism means to execute a program using multiple processors or cores (or, with hyper-threading, using multiple logical threads.) Programs can be executed in parallel even if they are written without primitives for concurrent programming. Different sub-expressions of an expression could be evaluated on different cores if evaluation is costly. In Java, stream pipelines can be processed sequentially or in parallel with minimal changes to the underlying code. Some programs do not require doing multiple things at the same time but still benefit from parallel execution on many cores.

Other programs do require an explicit notion of doing multiple things at the same time. Such programs must be written with special support from programming languages for managing multiple so called threads of execution. Different threads of a program can be regarded as seperate programs accessing the same memory and files. They can be (and usually are) coordinated, for example to wait for and consume results computed by other threads. Using and coordinating multiple threads of execution in a program is called concurrent programming.

Concurrent programs can be executed using parallelism if the executing computer has multiple cores. They can also be executed sequentially where a scheduler switches between different threads usually in quick succession. The order in which statements in different threads are executed is generally unspecified. Executing a concurrent program can have non-deterministic effects, meaning that different runs may behave differently, regardless of whether they are executed using parallelism or not.

In this tutorial, we will discuss different ways for using and coordinating threads in Java. First, we will compare different ways of using threads in a program for rendering images of the Mandelbrot fractal. The rendering of such images is costly and can benefit from parallel execution. Later, we will discuss how to coordinate different threads using locks in a concurrent algorithm for coloring grids. Coordinating threads can be tricky and we will discuss how to avoid blocking computations by different threads waiting for each other.

In the thrid part of this tutorial we introduce the actor model and how to use actors in Java.

The underlying source code is available online, and this tutorial includes tasks to extend it. If you want to follow along, you can use your own Java development environment or install Docker and VS Code with the Remote Development Extension Pack to use a predefined environment without creating (or adjusting) your own. To use the predefined environment in VS Code download and unpack the source code (or clone the repository using git), open the repository folder in VS Code, click on the Remote Containers icon in the bottom-left corner, and select Reopen Folder in Container.