VOLATILE VS
SYNCHRONIZED VS
LOCK IN JAVA
If you’ve been working with Java long enough, you’ve probably bumped into three familiar keywords when dealing with concurrency: volatile, synchronized, and Lock.
But here’s the truth, most developers think they understand them until the day something weird happens in production: a variable doesn’t update, a thread freezes, or a CPU spike appears out of nowhere. Then suddenly, concurrency feels like black magic.
Let’s break the magic into something far more human.
A Story: Your Team’s Whiteboard Problem
Imagine you and your teammates share one whiteboard during a sprint. Everyone writes tasks on it.
- check_circle Sometimes one person updates a task, but the others don't see the change immediately.
- check_circle Sometimes two people erase and write at the same time then creating chaos.
- check_circle Sometimes you want only one person at a time to touch the board.
This whiteboard is shared memory, and your developers are threads.
Now let’s map this to Java concurrency tools.
1. volatile: “Everyone sees the latest value.”
volatile is like telling everyone: “Hey, if someone updates this task on the whiteboard, all of you must read the newest version. No caching!”
What it does:
- check_circle Ensures visibility – all threads read the most updated value.
- check_circle Prevents threads from using stale values stored in CPU caches.
- check_circle Guarantees happens-before relationship when writing/reading.
What it doesn’t do:
- check_circle Does not provide atomicity.
- check_circle Does not protect the entire block of code.
- check_circle Does not prevent race conditions for compound actions.
Perfect example:
volatile boolean flag = true;
Every thread sees the latest change of flag.
But if you try:
volatile int count = 0;
count++; // not atomic
You will still get race conditions.
Developers often assume this is one action.
count++;
But actually, JVM breaks it into three steps:
- check_circle Read the current value of count
- check_circle Add 1 to the value
- check_circle Write the new value back
This is three separate operations, not one.
So if two threads run this at exactly the same time:
- check_circle Thread A reads count = 10
- check_circle Thread B reads count = 10
- check_circle Thread A writes 11
- check_circle Thread B writes 11
Person p = new Person("John", 20);
map.put(p, "Developer");
p.setAge(30); // Hash code changed!
map.get(p); // Returns nullImmutable objects keep hash codes consistent, making collections reliable.
4. Security
Mutable objects can be manipulated unexpectedly. Immutability:
- check_circle Prevents tampering
- check_circle Avoids unsafe state changes
- check_circle Makes objects safe to share across modules
This is why String is immutable in Java.
5. Easier Testing
Immutable objects simplify testing:
- check_circle Create → Test → Dispose
- check_circle No need to reset state
- check_circle Predictable results every time
6. Fits Functional Programming
Streams, lambdas, and functional patterns rely on pure data with no side effects.
Immutable objects integrate seamlessly with these modern Java features.
How to Create a Truly Immutable Class
Creating an immutable class in Java is simple if you follow a few rules:
1. Make the class final
Prevents subclassing, which can introduce mutability.
public final class Person { ... }2. Make all fields private final
Ensures fields are set once and not accessible for modification.
private final String name;
private final int age;
private final List<String> hobbies;3. No setters
Immutable = no ability to change fields after construction.
4. Defensive Copy for Mutable Fields
If your class has mutable objects (List, Map, arrays, Date), copy them in the constructor and return copies in getters.
public Person(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = List.copyOf(hobbies); // Immutable copy
}
public List<String> getHobbies() {
return hobbies; // Already immutable
}List.copyOf() is key: even if the original list changes, your object remains safe.
5. Return Copies for Custom Mutable Objects
For fields holding custom mutable objects, always create a defensive copy in the getter:
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = new Address(address); // copy constructor
}
public Address getAddress() {
return new Address(address); // defensive copy. prefent modified
}6. Optional: “Wither” Methods for Updates
Since immutable objects cannot change, you can create a new object with updated fields:
public Person withAge(int newAge) {
return new Person(this.name, newAge, this.hobbies);
}This pattern is clean, predictable, and fits modern functional design.
Modern Java: Use Records (Java 17+)
Records are immutable by default:
public record Person(String name, int age, List<String> hobbies) {
public Person {
hobbies = List.copyOf(hobbies); // Defensive copy
}
}Records handle most boilerplate automatically final fields, no setters, equals, hashCode, and toString.
Why List.copyOf() Is Important
Even though List is normally mutable, List.copyOf() returns a special immutable implementation:
- check_circle Changes to the original list don’t affect it
- check_circle Methods like add, remove, set throw UnsupportedOperationException
- check_circle Perfect for immutable fields
Unlike Collections.unmodifiableList(), which only wraps a mutable list (and can still be changed externally), List.copyOf() is truly immutable.
Final Thoughts
Immutable classes in Java are more than a nice-to-have. They provide:
- check_circle Thread safety
- check_circle Predictable behavior
- check_circle Safe usage in collections and caching
- check_circle Security and robustness
- check_circle Functional programming support
- check_circle Simplified testing
By following the simple rules:
- check_circle final class
- check_circle private final fields
- check_circle No setters
- check_circle Defensive copies
- check_circle Careful getters
You can write bulletproof, reliable, and clean Java code.
Immutability is one of the easiest ways to reduce bugs and improve code quality. Once you embrace it, your projects will feel more stable and easier to maintain.