When multiple threads work on the same data and the value of that data changes, the scenario is not thread-safe, resulting in inconsistent results. When a thread is already working on an object and preventing another thread from working on the same object, this process is called Thread Safety. This article explores what thread safety is, how it differs from non-thread-safe code, and techniques to make your Java applications thread-safe.
What is Thread Safety?
A thread-safe class or method ensures correct behavior and data consistency when accessed by multiple threads concurrently. For example, when a thread is already working on an object and preventing another thread from working on the same object, this process is called Thread Safety.
How to Achieve Thread Safety in Java?
There are four ways to achieve Thread Safety in Java. These are:
- Using Synchronization.
- Using Volatile Keyword.
- Using Atomic Variable.
- Using the Final Keyword.
Examples of Thread-Safe Classes in Java
These classes handle synchronization internally to ensure safe access by multiple threads.
Code Example: Thread Safe using Synchronization
// Thread-safe example using synchronized method
class Calculator {
// Synchronized method to ensure thread safety
synchronized void addNumbers(int base)
{
Thread current = Thread.currentThread();
for (int i = 1; i <= 5; i++) {
System.out.println(current.getName() + " : "
+ (base + i));
}
}
}
// Worker class extending Thread to perform operation
class WorkerThread extends Thread {
// Shared Calculator instance
Calculator calc = new Calculator();
@Override public void run()
{
// Calling the synchronized method
calc.addNumbers(10);
}
}
// Main class to run the thread-safe example
public class Geeks {
public static void main(String[] args)
{
// Creating an instance of WorkerThread
WorkerThread worker = new WorkerThread();
// Creating two threads that share the same
// WorkerThread instance
Thread threadOne = new Thread(worker);
Thread threadTwo = new Thread(worker);
// Naming the threads for clarity
threadOne.setName("Alpha");
threadTwo.setName("Beta");
// Starting both threads
threadOne.start();
threadTwo.start();
}
}
Explanation:
- Calculator class has a synchronized method addNumbers() to ensure thread-safe access.
- WorkerThread class extends Thread and creates its own Calculator instance.
- In run(), it calls addNumbers(10) using its Calculator instance.
- In main(), one WorkerThread object is shared by two threads (Alpha and Beta).
- Both threads start and call addNumbers(10) through the same WorkerThread.
- Problem: Each thread still uses its own Calculator, so synchronization is ineffective.
- Output may appear interleaved because threads are not actually sharing the same resource.
What is Non-Thread Safety?
Non-thread-safe class or method does not provide the correct answer when multiple threads execute simultaneously. When multiple threads access and modify shared data without proper synchronization, it can lead to:
- Data races
- Inconsistent state
- Unexpected behavior
- Crashes or corruption
Examples of Non-Thread-Safe Classes in Java
These classes do not handle synchronization internally and can lead to data inconsistency in multithreaded environments.
Code Example: Non-Thread Safe
// Non-thread-safe example without synchronized method
class Calculator {
// Method without synchronization - not thread-safe
void addNumbers(int base)
{
Thread current = Thread.currentThread();
for (int i = 1; i <= 5; i++) {
System.out.println(current.getName()+" : "+(base + i));
}
}
}
// Worker class extending Thread to perform operation
class WorkerThread extends Thread {
// Shared Calculator instance passed via constructor
Calculator calc;
WorkerThread(Calculator calc) { this.calc = calc; }
@Override public void run()
{
// Calling the non-synchronized method
calc.addNumbers(10);
}
}
// Main class to run the non-thread-safe example
public class Geeks {
public static void main(String[] args)
{
// Single shared Calculator object
Calculator sharedCalc = new Calculator();
// Creating two threads with the same Calculator
// instance
WorkerThread threadOne = new WorkerThread(sharedCalc);
WorkerThread threadTwo = new WorkerThread(sharedCalc);
// Naming the threads
threadOne.setName("Alpha");
threadTwo.setName("Beta");
// Starting both threads
threadOne.start();
threadTwo.start();
}
}
Explanation:
- The calculator has a non-thread-safe addNumbers() method.
- Two threads share the same Calculator object.
- Both threads call addNumbers(10) simultaneously.
- Since thereâs no synchronization, output may be interleaved or inconsistent
Comparison table of Thread Safety and Non-thread Safety
Features | Thread-Safe | Non-Thread Safe |
|---|---|---|
Definition | It is used to handle concurrent access by multiple threads safely | Not designed for safe concurrent access by multiple threads |
Synchronization | Uses synchronization internally for Thread safety | It does not use synchronization; access must be manually managed. |
Use Case | It is used in multi-threaded environments. | It is mainly used for single-threaded scenarios or with external sync. |
Performance | Slower due to synchronization overhead. |
|
| May not scale well under high concurrency due to lock contention | Scales well if used in a controlled single-threaded context |
| Vector, Hashtable, ConcurrentHashMap, StringBuffer | ArrayList, HashMap, StringBuilder, SimpleDateFormat |