Java & Threads
Threads are essentially subprocesses1:
informally, we can think of them as tasks that run "simultaneously" within a program.
For example, a web server application may have a number of threads running at a given time,
each responding to a different web page request. A graphics rendering application may have
different threads running, each rendering a different portion of an image. A strategy game
might have multiple threads running, each exploring different potential moves for an AI player
to make.
When multiple threads are in progress, the computer's operating system and processor(s) between them
determine how to actually distribute the threads among the available CPU resources. In a simple
hypothetical case where there are
two threads are running and there are two CPU cores available, we might suppose that the threads can
literally run simultaneously, one on each core. But the reality is usually muchg more complex:
- there will usually be a large number of threads competing for a small number of shared
resources (CPU cores, memory, even specific components of a given CPU core...);
- at a given moment in time, a thread that is "running" may actually not be able to progress
because it is waiting for some resource to become available (e.g. data from a particular source,
a lock on a particular file etc);
- on processors that support simultaneous multithreading (SMT), multiple threads can
actually be "executed" simultaneously on a single CPU core as far as the programmer is
concerned.
The complex process of allocating threads
to available resources is generally termed thread scheduling.
Java threading APIs
Despite the above complexities, multithreading is a necessity in all but the simplest of applications:
- in recent years, greater advances in processor "power" have been made in the form
of multicore/multiprocessor architecture rather than raw clock speed per se; to make the most
of a computer's processing power, we are likely to need to distribute the work across
multiple processors or cores;
- in other cases, the benefits of multithreading may have more to do with program flow and
logic: in our example of a web server processing incoming simultaneous equests,
processing different requests in separate threads makes the program easier to organise, even if
the hardware it is running on is likely to have only a few cores compared to the number of
concurrent requests.
Different applications, then, need to make different uses of multithreading. Because of these different
uses, and as typical uses have evolved over time, a number of different threading APIs have evolved
both in Java and in other modern programming langauges.
The Thread class and associated Runnable interface
and other methods
provide the lowest level API that the programmer generally needs to deal with.
This is the most "traditional" form of thread programming and is essentially the API that has been around since
the very first versions of Java. Java's Thread class, along with the simple thread methods
and a few other associated classes (notably Runnable) provide very broad-level features such as methods for:
As mentioned, the Thread API is essentially a low-level API. There is generally a close mapping between a Java Thread
object and an actual thread or process at the operating system level. Calls such as Thread.start(), Thread.sleep(),
Object.notify() etc will generally map quite closely to underlying operating system calls.
API does not define thread tasks in terms of logical concepts such as jobs, queues, time limits etc.
It is herefore
the keystone of thread programming, but not the be-all-and-end-all in complex multithreaded applications.
In particular, it does little to help the programmer to organise thread tasks into other structures that
might be required such as processing queues, task statuses and communication between subtasks.
For more information, see the Java Thread tutorial on the following page.
The Executor framework
In can be cumbersome to write complex multithreaded applications using the Thread API alone.
Therefore, the Java executor framework allows threads and their tasks to be defined in more "logical",
higher-level terms (e.g. "tasks" are "scheduled" or added to queues).
Even with the Executor framework, the task of splitting a given task into multiple parallel subtasks can be tedious.
The Java Stream API simplifies this for a class of tasks that can be
broadly categorised as divide and conquer. If the task can be organised into a "stream" of largely
independent items that can be split into sub-streams, then turning the task into a multithreaded one can
be as simple as replacing stream() with parallelStream(). (The reality of when this is actually
possibly and beneficial can also be much more complex!)
Virtual threads
A new API named Project Loom aims to offer a programming model similar to the
traditional Thread model but with improved performance. The idea is that threads presented to the programmer are actually "virtual"
threads. Tasks executed on different virtual threads as far as the programmer is concerned can be switched in and out of
a smaller number of actual threads, all by the JVM, without the overhead of operating system calls to achieve this switching.
Getting started with threads in Java
On the next page, we look at how to start using threads in Java. It is recommended
you then look at the other APIs mentioned above if you are planning to work on more advanced and complex multithreaded applications in Java.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.