These two articles of Alex Miller:
clearly explain why using
final
fields in concurrent programs is extremely powerful and safe (thread-safe).
Take a look a this simple example:
public class SomeClass implements Runnable {
private final LinkedHashMap map;
public SomeClass(LinkedHashMap copiedMap) {
map = copiedMap;
preProcessMap();
}
private void preProcessMap() {
//some non-trivial and not-so-short computations
}
// A read method
public Object get(Object key) {
return map.get(key);
}
public void run() {
//assume that in this method only
//unsynchronized read operations on map may occur
//any unsynchronized write operation is not thread-safe
}
// etc...
}
public class Main {
public static void main(String[] args) {
LinkedHashMap map = // map creation
// map initialization
new Thread(new SomeClass(map)).start();
// etc.
}
}
UPDATE:I preserved the original version of the code but after some thoughts and thanks to some comments (thank you dear readers) I conclude that this example is not 100% correct. Before
Thread.start()
could be started the object has to be fully initialized and initialization will be completed only AFTER the
SomeClass
object is created. This way
final
keyword doesn't add any value there. On the other hand I meant this piece of code:
public class Main {
public static void main(String[] args) {
LinkedHashMap map = // map creation
// map initialization
final ExecutorService service = Executors.newFixedThreadPool(1);
service.submit(new SomeClass(map));
// etc.
}
}
In such case I'm pretty sure you cannot assume that e.g. second processor (in a multi-core platform) will wait before executing newly scheduled task until the object of class
SomeClass
is fully initialized, right? Correct me if I'm wrong because I feel have some mess in my brain now ;)
End of UPDATEWhatever happens in the
preProcessMap()
method you can be sure that
LinkedHashMap
will be copied and preprocessed BEFORE starting the
run()
method by the new thread. This way any READ (get) operation on this map in all threads will be safe and will operate on completely and correctly initialized
map
object.
If only you remove
final
keyword from
LinkedHashMap
declaration you cannot be sure our previous assumption. It may happen that the thread will be started BEFORE copying the map and if in
run()
method you process this map in any way you may get NPE.
Let me use a bottle metaphor :) For me
final
keyword is like the guarantee that you closed the bottle of whisky before passing it to your crazy friend who likes juggling them. If you ensure that the bottle is closed (final) before passing it to your friend you can be sure he will not spill any of the precious whisky on the floor. On the other hand if you start passing the bottle to him while trying to close it you're not sure whether it's fully closed while he starts juggling it or not. It may be closed 100% of time when you meet in your place but when you go to your other friend's party he may spill very expensive whisky and break the bottle in the same time (test and production environments metaphor this time).
Conclusion? Close the bottle before giving it to anybody else.
I will not be original and simply copy Alex Miller's advice:
Final fields are ever so important and useful. Whenever possible, strive to make your fields final. One tip for this is to enable a Save Action in Eclipse (or your IDE of choice) that automatically attempts to make fields final if possible so you can’t forget!