In a multi-threaded environment, there is always a race between two or more threads attempting to update the mutable shared data at the same time. Java has offered a mechanism where we can avoid these race conditions. The mechanism is basically the synchronization of threads which are accessing the shared data. To achieve this, we mark a piece of logic or code block with java synchronized keyword, thus allowing only one thread to execute at any given time.
Java Synchronized Keyword
The synchronized keyword can be used on three different levels:
- Instance methods
- Static methods
- Code blocks
To provide synchronization, Java internally uses a monitor which is also known as monitor lock or intrinsic lock. These monitors are bound to an object, thus all synchronized blocks of the same object can have only one thread executing them at the same time. In a more simpler terms, we can say that a thread that needs exclusive and consistent access to an object’s fields has to acquire the object’s monitor (intrinsic lock) before accessing them, and then release the intrinsic lock when it’s done with them. A thread is said to own the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock.
Synchronized Instance Method
To make a method synchronized, simply add the synchronized
keyword to its declaration:
public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } }
If count
is an instance of SynchronizedCounter
, then making these methods synchronized has two effects:
- First, when one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
- Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.
Synchronized Static Methods
Static methods are synchronized
just like instance methods
public class SynchronizedCounter { private int c = 0; public static synchronized void increment() { c++; } public static synchronized void decrement() { c--; } public static synchronized int value() { return c; } }
When a static synchronized
method is invoked, the thread acquires the intrinsic lock for the Class
object associated with the class. Thus access to class’s static fields is controlled by a lock that’s distinct from the lock for any instance of the class.
Synchronized Code Blocks
Sometimes the situations demand not to synchronize the entire method but only some instructions within it. This can be achieved by applying synchronized to that particular code block:
public class SynchronizedCodeBlock { public void addName(String name) { synchronized(this) { lastName = name; nameCount++; } nameList.add(name); } }
In the above example, the method addName
synchronizes the changes to lastName
and nameCount
and avoid synchronizing invocations of other objects’ methods.
Conclusion
In this quick journal entry, we have seen different ways of using the synchronized
keyword to achieve thread synchronization and handle the race condition between multiple threads.