The aim of this article is to introduce you to Java multithreading – multithreaded processing in Java, explain its principles, advantages and disadvantages.

Multitasking – what it is

Multitasking allows multiple activities to take place on one computer at the same time. for example multiple programs running in parallel on a computer, where the programmer compiles code and listens to music at the same time, while the operating system is being 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 be executed concurrently 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 small

chart of fibre and process differences
Threads vs Processes

Multithreading – why use it?

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. In the case of waiting for user input, CPU computation cycles are wasted. However, in a multi-threaded program, the user’s data could be analyzed or checked in the background at such a time.

Similarly, singlethreaded GUI programs that use the same thread for time-consuming computations on the processor may cause the GUI to freeze during the computation and thus reduce the user experience.

Threads

A thread in the program is executed sequentially and independently of the others. Thus, a number of threads sharing a common address (memory) space can run concurrently during program execution and thus can exchange data.

Java threads

There are 3 concepts related to multithreading in Java:
1. creating threads and providing the code that will be executed by the thread
2. access to common data and code via synchronization
3. transition between different thread states

Java main thread

When a Java program is run, a user thread is created to execute the main() method of the application. This thread is also called as the main thread. If no additional threads are created during execution, the program terminates after execution. Additional threads are created from the main thread, and the program remains running after the main method finishes until all user threads have finished.

Creating a thread

In Java, a thread is represented by an object of class Thread The thread can be created in one of two ways:
1. By implementing the java.lang.Runnable interface
2. 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 most appropriate for only one thread to have access to shared resources at a time. 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 synchronisation 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 has already acquired a lock on it.

If a thread cannot immediately obtain a lock, it is locked, that is, it must wait for a lock to become available. If a thread leaves a shared resource, the system runtime will ensure that the lock is removed, and if a thread is waiting for access, that thread can gain access after the lock is acquired.

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

Synchronisation methods

If 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 further call other synchronized methods of the object without being blocked. Unsynchronized object methods can be called at any time by other threads.

Thread safety

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

Java thread life cycle

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

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 to get 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 pending state can be woken up if one of these 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. Thread wait time expires.
3. Another thread interrupts the waiting thread.

Recovery status

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

Expiry status

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

Status of interruption

Occurs when another thread invokes the interrupt() method on the waiting thread. The awakened thread is activated, but the return value from the wait() method ends up raising an IntteruptedException exception as soon as the awakened thread takes its turn. The code that raises this exception must be ready to handle it.

Thread priorities

Priorities are integer values from 1 (the lowest priority given by the constant Thread.MIN_PRIORITY) to 10 (the highest priority given by the constant Thread.MAX_PRIORITY). Default value is 5 (Thread.NORM_PRIORITY). A thread inherits its priority from its parent thread. Thread 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

This is a situation where one 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() The task to be executed is defined in this method.
sleep() The running process is paused for some time.
yield() Logs the execution of the current thread and allows the execution of a pending other thread with the same or higher priority.
stop() Stops thread execution permanently.
join() The thread will wait for another thread to finish execution.
isAlive() Checks whether the thread is still alive or not. Returns a boolean value (true/false) that indicates whether the thread is running or not.
setPriority() Set thread priority.

Benefits of Java multithreading

Multithreading in Java improves performance and reliability, while drastically reducing the time it takes to execute a program and thus reducing the software’s operating costs. The processor and other hardware resources are thus used more efficiently.

Disadvantages of Java multithreading

Multithreading in Java increases complexity in terms of code debugging, while increasing the likelihood of deadlock in process execution. The order of thread execution is not in the hands of the developer, so each execution of the program can take a different course. Complications can also appear when porting the program to other platforms.

Multitasking vs 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 execute concurrently. Multitasking is used to manage multiple processes, while multithreading is used to improve the performance of a single process.

Multitasking vs 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 execute multiple processes simultaneously. Multitasking is used to manage multiple processes on one 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 display correctly on a mobile device, tap on 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 processors (or cores).
Resource sharing. Resources (CPU, memory) are shared among programs. Resources (CPU, memory) are shared among tasks. Resources (CPU, memory) are shared among threads. Each process has its own set of resources (CPU, memory).
Planning For allocating CPU time to programs, scheduling mechanisms such as round-robin or priority-based scheduling are used. For allocating CPU time to tasks, scheduling mechanisms based on priorities or time slicing are used. It uses scheduling based on priority or time slicing 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.
Context switching Context switching is required to switch between programs. Context switching is required to switch between tasks. Context switching is required to switch between threads. Context switching is required to switch between processes.
Inter-process communication (IPC) It uses message passing or shared memory for IPC. It uses message passing or shared memory for IPC. It uses thread synchronization mechanisms (e.g., locks, semaphores) for IPC. It 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 quite a complex topic, much more could be written about it and go into even more depth, ideally with examples. I therefore 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