Understanding Immutable Classes In Java
I recently reviewed an article that claimed immutable classes in Java are not always thread safe. Ultimately, this is not true since any immutable class cannot contain state and therefore no two threads could change it's state.
To understand this more, I would like to do a quick dive into what an Immutable class is in Java.
In order for a class to be immutable it must be stateless. This means it's properties should never change once the class is initialized. Anything that can change state must be handled within the methods of the class.
Example Stateless Class:
This is where people say that immutable classes are not always thread safe.
Example Immutable becomes mutable
Pair is still Immutable because, it never broke it's contract. The state of the pair did not change, but the state of the provided instance did.
Notice any time we provide access to the book, or need to change a property, we copy the book as to ensure it's statefull parts never change. This, in turn protects the state of the instance.
Summary
An immutable class is a class that cannot change it's state after instantiation. For this to happen, all class fields must be marked final and any Mutable fields must be marked private and state never changed internally to the class.
It is important for developers to uphold the contract of immutability if laid claim. If the contract is held, an immutable class is always thread safe. Understanding how the class is used will always guarantee you do not misinterpret the contract. Read the docs, browse the source, ask a teammate if unsure. An immutable class is your best friend, as it is the easiest instance to manage and trust.
To understand this more, I would like to do a quick dive into what an Immutable class is in Java.
Proerties of an immutable class in Java
In order for your class to be truly immutable, it must meet the following cirteria:- All class members are declared final.
- All variables used in a class at the class level must be instantiated when the class is constructed.
- No class variable can have a setter method.
- This is implied from the first statement, but want to make it clear that you cannot change the state of the class.
- All child object must be immutable as well, or their state never changed in the immutable class.
- If you have a class with mutable properties, you must lock it down. Declare it private, and ensure you never change it's state. More on this at the end.
In order for a class to be immutable it must be stateless. This means it's properties should never change once the class is initialized. Anything that can change state must be handled within the methods of the class.
Example Stateless Class:
public class ImmutablePair<I, J> {
private final I first;
private final J second;
public ImmutablePair(I first, J second) {
this.first = first;
this.second = second;
}
public I getFirst(){
return this.first;
}
public J getSecond(){
return this.second;
}
}
The Immutable class holds up it's end of the bargain. Any call into the class will never change the state of an instance. If one thread instantiates this class, and two threads begin using the object, there is no chance that they will get a different value for the pair on a call to getFirst()
since neither can reset the values of the pair.Mutable objects invading an immutable class
In the pair example, we have guaranteed the thread safety of the class by making it immutable. There is a catch 22 that the user creating the pair is not providing statefull values for first and second properties.This is where people say that immutable classes are not always thread safe.
Example Immutable becomes mutable
class MutableBook {
private String title;
public String getTitle(){
return this.title;
}
public void setTitle(String title){
this.title = title;
}
}
Say a develper stores books in the Pair
.
MutableBook effectiveJava = new MutableBook();
effectiveJava.setTitle("Effective Java");
MutableBook javaConcurrency = new MutableBook();
javaConcurrency.setTitle("Java Concurrency in Practice");
ImmutablePair<MutableBook, MutableBook> javaBookPair =
new ImmutablePair(
effectiveJava, javaConcurrency
);
Pair has held up it's end of the bargain, but since the values are mutable it does contain state, and the pair object is not technically thread safe. Or should I say, the objects in the pair are not thread safe.// Lambda thread task. Java 8 lambda = black box of awesome.
Runnable task1 = () -> { javaBookPair.getFirst().setTitle("Task 2 May not see the change.") };
Runnable task2 = () -> { javaBookPair.getFirst().getTitle() };
new Thread(task1).start();
new Thread(task2).start();
Note: The keyword volatile
for MutableBook's title property would allow synchronization across threads, and task two would see the change.Pair is still Immutable because, it never broke it's contract. The state of the pair did not change, but the state of the provided instance did.
Immutable Classes w/ Mutable Properties
An Immutable class is likely to hold a property that can contain state. In order for the Immutable class to uphold it's contract:- It must never provide access to the mutable class. Mark it private.
- It will never change the state of the mutable class internally.
public class ImmutableReader {
private final MutableBook readersBook;
private final int page;
public ImmutableReader(MutableBook book) {
this(book, 0);
}
private ImmutableReader(MutableBook book, int page){
this.page = page;
// Make copy to ensure this books state won't change.
MutableBook bookCopy = new MutableBook();
bookCopy.setTitle(book.getTitle());
this.readersBook = bookCopy;
}
public MutableBook getBook() {
// Do not return the book, but a new copy. Do not want the readers
// book to change it's state if developer changes book after this call.
MutableBook bookCopy = new MutableBook();
bookCopy.setTitle(this.readersBook.getTitle());
return bookCopy;
}
public int getPage() {
// primitives are already immutable.
return page;
}
/**
* Must return reader instance since it's state has changed.
**/
public ImmutableReader turnPage() {
return new ImmutableReader(this.readersBook, page + 1);
}
}
Notice any time we provide access to the book, or need to change a property, we copy the book as to ensure it's statefull parts never change. This, in turn protects the state of the instance.
Summary
An immutable class is a class that cannot change it's state after instantiation. For this to happen, all class fields must be marked final and any Mutable fields must be marked private and state never changed internally to the class.
It is important for developers to uphold the contract of immutability if laid claim. If the contract is held, an immutable class is always thread safe. Understanding how the class is used will always guarantee you do not misinterpret the contract. Read the docs, browse the source, ask a teammate if unsure. An immutable class is your best friend, as it is the easiest instance to manage and trust.
Please send me the complete source code of bookreader java example.
ReplyDelete