As a developer you often have to initialize static variables in a multi-threaded environment. The basic solution that most programmers apply is:
1 2 3 4 5 6 7 8 9 | private static Object staticVar = null; public static synchronized Object getStaticVar() { if (staticVar == null) { // initialize staticVar = ... } return staticVar; } |
This is a simple but expensive method. Each thread that needs the variable must synchronize with each other although the variable has long been initialized. So, usually the next step is to synchronize less:
1 2 3 4 5 6 7 8 9 10 11 12 | private static Object SYNCHRONIZER = new Object(); private static Object staticVar = null; public static Object getStaticVar() { if (staticVar == null) { synchronized (SYNCHRONIZER) { // initialize staticVar = ... } } return staticVar; } |
Yep. That does it, doesn’t it? The answer is: half! Imagine two simultaneous threads entering the method at the same time. If both will see staticVar
being null
then both will try to enter the synchronized block. And of course, both will initialize the variable nevertheless another thread did it before. So, we add another evaluation to ensure that only one thread will initialize:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private static Object SYNCHRONIZER = new Object(); private static Object staticVar = null; public static Object getStaticVar() { if (staticVar == null) { synchronized (SYNCHRONIZER) { if (staticVar == null) { // initialize staticVar = ... } } } return staticVar; } |
Most books end here but omit a very crucial part. The code works perfect as long as the initialization is a simple operation only. Let’s make the initialization a bit more sophisticated:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private static Object SYNCHRONIZER = new Object(); private static List<String> staticVar = null; public static List<String> getStaticVar() { if (staticVar == null) { synchronized (SYNCHRONIZER) { if (staticVar == null) { // initialize staticVar = new ArrayList<String>(); staticVar.add("value 1"); staticVar.add("value 2"); staticVar.add("value 3"); } } } return staticVar; } |
The first glance doesn’t reveal anything. But it happened several times in one of my own applications that two threads were not correctly synchronized. Occasionally, one thread found the list to be empty. What happened?
The magic is that staticVar
is being set at the very first beginning of the synchronized block. Meanwhile another thread was entering the method and saw the variable not being null
. It immediately started using the list although it was not yet initialized completely. The correct solution is therefore:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private static Object SYNCHRONIZER = new Object(); private static List<String> staticVar = null; public static List<String> getStaticVar() { if (staticVar == null) { synchronized (SYNCHRONIZER) { if (staticVar == null) { // initialize List<String> tmp = new ArrayList<String>(); tmp.add("value 1"); tmp.add("value 2"); tmp.add("value 3"); staticVar = tmp; } } } return staticVar; } |
For readability, we could refactor the method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private static Object SYNCHRONIZER = new Object(); private static List<String> staticVar = null; public static List<String> getStaticVar() { if (staticVar == null) { synchronized (SYNCHRONIZER) { if (staticVar == null) { staticVar = createList(); } } } return staticVar; } private static List<String> createList() { // initialize List<String> tmp = new ArrayList<String>(); tmp.add("value 1"); tmp.add("value 2"); tmp.add("value 3"); return tmp; } |