The aim of this article is to introduce you to Java multithreading, its principles, advantages and disadvantages.

Java multitasking meaning

Multitasking allows multiple activities to take place on a computer at the same time, for example, multiple programs running in parallel on a computer, where the developer compiles code and listens to music at the same time, while the operating system is updated in the background.

Multitasking can be based on processes and threads. Threads can be part of processes, and these allow parts of the same program (process) to run simultaneously on a computer.

Thread vs Process

– threads share the same address space
– the overhead of context switching between threads is typically less demanding than that between processes.
– the communication requirements between threads are relatively low

chart of thread and process differences
Threads vs Processes

Why use multithreading

In a single-threaded environment, only one task can be running at a time. An example in Java might be the main() method, where the program flow is executed sequentially. Waiting for user input wastes CPU cycles. However, in a multi-threaded program, the user’s data could be analyzed or checked in the background at such a time.

Similarly, single-threaded GUI programs that use the same thread for time-consuming computations on the processor can cause the GUI to freeze during the computation, reducing the user experience.

Threads

A thread in a program is executed sequentially and independently of the others. This allows a number of threads that share a common address (memory) space to run concurrently during program execution and exchange data.

Java threads

There are 3 concepts related to multithreading in Java:
1. creation of threads and the provision of code to be executed by the thread
2. access to shared data and code through synchronization
3. transition between different thread states

Java main thread

When a Java program runs, a user thread is created to execute the main() method of the application. This thread is also called the main thread. If no additional threads are created during execution, the program will terminate after execution. Additional threads are created from the main thread and the program continues to run after the main method has finished until all user threads have finished.

Creating a thread

In Java, a thread is represented by an object of the Thread class. A thread can be created in one of two ways:
1. By implementing the java.lang.Runnable interface
2. By inheriting from the java.lang.Thread class

Thread synchronization

Threads can share resources because they share the same address space. However, there are critical situations where it is best that only one thread at a time has access to shared resources. An example would be ordering concert tickets in an information system where customers compete with each other, each trying to get the best seat, but only one of them can order each of the tickets. So in the actual process of buying a ticket, only one customer needs to have access to that ticket.

Thread synchronization rules

A thread must acquire a lock on a shared resource before it can use it. The system runtime ensures that no other thread can access a shared resource if another thread already has a lock on it.

If a thread cannot get a lock immediately, it is locked, i.e. it must wait for a lock to become available. When a thread leaves a shared resource, the system runtime ensures that the lock is released, and if a thread is waiting for access, it can gain access after the lock has been acquired.

The program itself cannot make any assumptions about the order of the threads that will receive the lock, because this is decided by the operating system, not by the developer. Without proper synchronization, two or more threads would update the same value at the same time, leaving it in an undefined or inconsistent state.

Synchronization methods

When a thread is inside a synchronized method object, all other threads that need to execute code from the synchronized method must wait as well. This restriction does not apply to a thread that has acquired a lock and is executing a synchronized object method.

Such a method can continue to call other synchronized methods of the object without being blocked. Unsynchronized object methods can always be called by other threads.

Thread safety

This term is used to describe classes that are designed to ensure that the state of their objects is always consistent, even when objects are being used by multiple threads simultaneously. An example of this is the StringBuffer class.

Java thread lifecycle

The following diagram shows all the possible states that threads go through in Java.

Thread status diagram
Thread status diagram

New – the state of a newly created thread that has not yet started execution.
Runnable – either executing or ready to execute, but still waiting for resource allocation.
Blocked – waiting for a lock to enter a synchronized method or block.
Waiting – waiting for another thread to perform some action, with no time limit.
Timed waiting – Same as waiting, but with a time limit.
Terminated – the thread has completed its execution.

Waiting status

A thread in a waiting state can be woken up when one of three events occurs:
1. Another thread calls the notify() method on the waiting thread’s object, and the waiting thread is selected as the thread to wake up.
2. The waiting time of the thread expires.
3. Another thread interrupts the waiting thread.

Recovery status

Calling the notify() method on an object wakes up one of the threads waiting to get a lock on that object. The choice of which thread to wake depends on the thread management implemented by the JVM. The awakened thread is not immediately ready to execute, but first changes state to acquire the lock. The thread is also removed from the list of waiting threads for this object.

Expiry status

The call to the wait() method specifies the amount of time it will take for the thread to expire if it is not woken up by a notification.

Interruption status

Interruption occurs when another thread calls the interrupt() method on the waiting thread. The awakened thread is enabled, but the return value from the wait() method ends up throwing an IntterruptedException when the awakened thread takes its turn. The code that throws this exception must be ready to handle it.

Thread priorities

Priorities are integers from 1 (the lowest priority given by the constant Thread.MIN_PRIORITY) to 10 (the highest priority given by the constant Thread.MAX_PRIORITY). The default value is 5 (Thread.NORM_PRIORITY). A thread inherits its priority from its parent. A thread’s priority can be set using the setPriority() method and retrieved using the getPriority() method, both of which are defined in the Thread class. It should be emphasized that the priority setting is a recommendation for the Java Virtual Machine (JVM) and the JVM does not have to follow it.

Deadlock

Deadlock This is a situation where a thread is waiting for a lock held by another thread, and that thread is waiting for another lock held by the first thread.

The most important methods of the Thread class

start() initializes the thread and calls the run() method internally.
run() defines the task to be executed.
sleep() pauses the running process for some time.
yield() logs the execution of the current thread and allows the execution of a pending thread with the same or higher priority.
stop() permanently stops the execution of the thread.
join() the thread waits for another thread to finish its execution.
isAlive() checks if the thread is still alive. It returns a boolean value (true/false) indicating whether the thread is running or not.
setPriority() sets the priority of the thread.

Java multithreading advantages

Multithreading in Java improves performance and reliability, while drastically reducing the time it takes to execute a program, thereby reducing the cost of ownership of the software. It makes more efficient use of the processor and other hardware resources.

Java multithreading disadvantages

Multithreading in Java increases the complexity of debugging code, while increasing the likelihood of deadlocks in process execution. The order in which threads are executed is not in the hands of the developer, so each execution of the programme can take a different course. Complications may also arise when porting the program to other platforms.

Java Multitasking vs Java Multithreading

The main difference between multitasking and multithreading is that multitasking involves running multiple independent processes or tasks, while multithreading involves splitting a single process into multiple threads that can be executed simultaneously. Multitasking is used to manage multiple processes, while multithreading is used to improve the performance of a single process.

Java Multitasking vs Java Multiprocessing

The difference between multitasking and multiprocessing is that multitasking involves running multiple independent processes or tasks on a single processor, while multiprocessing involves using multiple processors to run multiple processes simultaneously. Multitasking is used to manage multiple processes on a single processor, while multiprocessing is used to improve system performance by allowing multiple processes to run simultaneously on multiple processors.

Multiprogramming vs Multitasking vs Multithreading vs Multiprocessing

To view correctly on a mobile device, tap the table image.

Multiprogramming Multitasking Multithreading Multiprocessing
Definition Running multiple programs on a single CPU Running multiple tasks (applications) on a single CPU Running multiple threads within a single task (application) Running multiple processes on multiple CPUs (or cores)
Sharing resources Resources (CPU, memory) are shared between programs Resources (CPU, memory) are shared between tasks Resources (CPU, memory) are shared between threads Each process has its own set of resources (CPU, memory)
Planning Uses round-robin or priority-based scheduling to allocate CPU time to programs Uses priority-based or time-segmented scheduling to allocate CPU time to tasks Uses priority-based or time-segmented scheduling to allocate CPU time to threads Each process can have its own scheduling algorithm
Memory management Each program has its own memory space Each task has its own memory space Threads share memory space within a task Each process has its own memory space
Switching context Requires context switching to switch between programs Requires context switching to switch between tasks Requires context switching to switch between threads Requires context switching to switch between processes
Interprocess communication (IPC) Uses messaging or shared memory for IPC Uses messaging or shared memory for IPC Uses thread synchronization mechanisms (e.g. locks, semaphores) for IPC Uses inter-process communication mechanisms (e.g. pipes, sockets) for IPC

Other recommended resources

The aim of this article was to familiarize the reader with multithreading in Java, to explain its principles, advantages and disadvantages. As this is a fairly complex topic, much more could be written about it, going into even greater depth, ideally with examples. I recommend the following resources to broaden your knowledge:

About the author

Jozef Wagner

Java Developer Senior

Viac ako 10 rokov programujem v Jave, momentálne pracujem v msg life Slovakia ako Java programátor senior a pomáham zákazníkom implementovať ich požiadavky do poistného softvéru Life Factory. Vo voľnom čase si rád oddýchnem v lese, prípadne si zahrám nejakú dobrú počítačovú hru.

Let us know about you