This post covers must know Java Multithreading basics – Heap Vs Stack, Thread-safety & Synchronization. When you have a multithreaded Java application, you need to code in a thread-safe manner. Java interviewers may ask you to detect thread-safety issues as discussed in here.
1.What is wrong with the following code?
A very simple code that should print numbers from 7 to 21. But does it?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import java.util.concurrent.TimeUnit; class Counter extends Thread { //instance variable Integer count = 0; // method where the thread execution will start public void run() { int fixed = 6; //local variable for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ": result=" + performCount(fixed)); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } // let’s see how to start the threads public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + " is executing..." ); Counter counter = new Counter(); //5 threads for (int i = 0; i < 5; i++) { Thread t = new Thread(counter); t.start(); } } //multiple threads can access me concurrently private int performCount(int fixed) { return (fixed + ++count); } } |
2. Above code is NOT Thread-safe
If you run it multiple times, you will see that some numbers get repeated as shown below. You get five”15″s and three “12”s. The result will be unpredictable and you will get different results each time you run it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
main is executing... Thread-1: result=7 Thread-2: result=8 Thread-3: result=9 Thread-4: result=10 Thread-5: result=11 Thread-2: result=12 Thread-3: result=12 Thread-4: result=12 Thread-5: result=13 Thread-1: result=14 Thread-2: result=15 Thread-3: result=15 Thread-4: result=15 Thread-5: result=15 Thread-1: result=15 |
3. What is happening under the covers in terms of Heap Vs Stack memory & thread-safety
As shown below in the diagaram, the local variable “fixed”, and the reference “counter” to the instance of the class “Counter” are stored in the stack. The instance of “Counter”, i,e. the object itself is stored in the heap. So, it will be shared by all the threads. The “++count” operation is not atomic and performs 3 operations under the covers:
Step 1: get value of count from heap
Step 2: add 1 to count (i.e. count = count + 1)
Step 3: write the new value back to the heap memory
So, it is possible that 5 threads read the same value of say “count = 8” and increment them all to “9”, and then when added with the fixed value of 6, resulting in five “15”s. Each time you run, you get different results. The above code is unpredictable.
4. How to fix the concurrency issue?
The above thread safety issue can be fixed two ways by controlling the access to the shared object “counter”.
Solution 1: Synchronized i.e. a lock on the performCount() method
This will put a lock on “counter” object so that when one thread is performing the other threads have to wait for the lock.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import java.util.concurrent.TimeUnit; class Counter extends Thread { //instance variable Integer count = 0; // method where the thread execution will start public void run() { int fixed = 6; for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ": result=" + performCount(fixed)); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } // let’s see how to start the threads public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + " is executing..." ); Counter counter = new Counter(); //5 threads for (int i = 0; i < 5; i++) { Thread t = new Thread(counter); t.start(); } } private synchronized int performCount(int fixed) { return (fixed + ++count); } } |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
main is executing... Thread-1: result=7 Thread-3: result=8 Thread-2: result=9 Thread-4: result=10 Thread-5: result=11 Thread-4: result=13 Thread-1: result=12 Thread-2: result=14 Thread-3: result=15 Thread-5: result=16 Thread-4: result=18 Thread-3: result=19 Thread-1: result=20 Thread-5: result=21 Thread-2: result=17 |
Why is locking of a method for thread safety is called “synchronized” and not “locked”?
When a method or block of code is locked with the reserved “synchronized” key word in Java, the memory (i.e. heap) where the shared data is kept is synchronized. This means,
When a synchronized block or method is entered after the lock has been acquired by a thread, it first reads (i.e. synchronizes) any changes to the locked object from the main heap memory to ensure that the thread that has the lock has the current info before start executing.
After the synchronized block has completed and the thread is ready to relinquish the lock, all the changes that were made to the object that was locked is written or flushed back (i.e. synchronized) to the main heap memory so that the other threads that acquire the lock next has the current info.
[ Further Reading: 7 Things you must know about Java locks and synchronized key word ]
Solution 2: AtomicInteger so that the increment operation is atomic
The “incrementAndGet()” on AtomicInteger happens atomically so that two or more threads cannot read the same value and increment them to the same result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; class Counter extends Thread { //instance variable AtomicInteger count = new AtomicInteger(); // method where the thread execution will start public void run() { int fixed = 6; for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ": result=" + performCount(fixed)); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } // let’s see how to start the threads public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + " is executing..." ); Counter counter = new Counter(); //5 threads for (int i = 0; i < 5; i++) { Thread t = new Thread(counter); t.start(); } } private int performCount(int fixed) { return (fixed + count.incrementAndGet()); } } |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
main is executing... Thread-1: result=7 Thread-2: result=8 Thread-3: result=9 Thread-4: result=10 Thread-5: result=11 Thread-1: result=16 Thread-3: result=13 Thread-2: result=12 Thread-4: result=15 Thread-5: result=14 Thread-1: result=20 Thread-5: result=19 Thread-2: result=21 Thread-4: result=17 Thread-3: result=18 |
What does atomicity mean?
Learn more about atomicty:
1) 10+ Atomicity, Visibility, and Ordering interview Q&As in Java multi-threading