Understanding Java volatile and synchronized is essential for Java developers, as it helps them maintain safe, concurrent access of data in a multi-threaded environment. With this guide, we’ll explore what each of these concepts means, their similarities and differences, and useful examples. After reading this article, you should have a better grasp on when and why to use volatile and synchronized in your code.
What is Volatile in Java?
Volatile is a keyword in Java language that marks an attribute or variable as “volatile”—meaning it can be accessed and changed concurrently by multiple threads. This keyword is used to alert the JVM (Java Virtual Machine) that the variable could be changed unexpectedly. The volatile keyword guarantees visibility of changes across threads – meaning regardless of age of the cached copies, all threads will see the same value. It also ensures that each read is taken directly from main memory and not thread-local caches.
Since a volatile variable is not cached, it can be slightly slower to use in comparison to other immutable variables. Nevertheless, the advantages of having reliable and read-write permission granted across threads make it a great practice. It ensures safe concurrent access to data and can be used to check for certain conditions in a loop.
What is Synchronized in Java?
Synchronized is another keyword used to mark a method or block of code as “synchronized” and guarantee mutual exclusion, so that only one thread can execute the code at a time. This keyword has been part of the Java language since version 1.0 and serves an important role in enabling thread-safe execution. The synchronized keyword is used around a method or a block of code to add lock to the code segment.
The advantage here is that the lock is acquired on class level (static synchronized) or on object level lock (non-static synchronized), which makes it suitable for cases when synchronization needs to be done between threads. Furthermore, synchronized blocks are implemented using monitors in Java and the waiting threads will get notified when the objects lock can be acquired.
Differences Between Volatile and Synchronized
Both volatile and synchronized keywords have a role in supporting thread safety. However, they accomplish this in different ways.
- Visibility: Volatile makes values written by one thread visible to other threads immediately. It achieves this by telling the JVM not to retrieve any cached copies of the field in question, only main memory values.
- Threads: Synchronized restricts access around a particular piece of code to one thread at a time. No other thread can enter the same piece of code until the original thread exits it. It does this by using locking mechanisms.
- Use Cases: As accessibility is the primary focus for volatile variables, they are used for synchronization issues related to visibility. Synchronization is used for control access problem where multiple threads compete for shared resources.
Uses of Volatile and Synchronized
Volatile is best used to guarantee visibility across threads. When you need a variable to serve as a “checkpoint” so that multiple threads modify or refer to data coherently, using volatile becomes important. For example, if you have multiple threads working together to create an object, using volatile could stop those threads from working on incomplete or inconsistent objects.
Similarly, synchronized keyword is useful when it’s necessary to ensure only one thread at a time can execute a critical section of code. It prevents race conditions from happening where multiple threads attempt to modify a single resource at the same time. Critical sections of code that must be accessed sequentially – such as methods that rely on in-memory caches – should use synchronized for thread safety.
Pros and Cons of Volatile and Synchronized
Volatile and synchronized can both help you write thread-safe code, but their ways of implementation can lead to pros and cons.
- Volatile:
- Pros: Simple implementation, lightweight, fast read and write operations.
- Cons: Does not provide absolute protection across threads.
- Synchronized:
- Pros: Guarantees mutual exclusion; visibility of changes across threads.
- Cons: Costly process as threads must acquire a lock before accessing code; holding locks on shared resources can also lead to deadlocks.
Performance Impact of Using Volatile and Synchronized
Using any kind of synchronization comes with a performance cost – as mentioned earlier, being able to access objects across multiple threads requires each thread to acquire a lock on the resource before accessing it. This can cause an overhead on the performance of an application, leading to slower and more resource consuming operations.
Similarly, when it comes to volatile variables, because the changes are taken from main memory instead of the thread-local cache, this comes with an performance impact due to frequent read and write operations on memory.
Examples of Java Volatile and Synchronized Usage
Suppose you have a resource (an object) that multiple threads are trying to use simultaneously. To prevent race conditions where two or more threads overwrite data while modifying the object, you can use synchronized keyword around key parts of the code that are updating the data.
Example:
~~~ public class MyResource { public synchronized void updateResource() { // update logic goes here } public void useResource() { // read logic goes here } } ~~~
In this example, any attempt by multiple threads to access useResource method will be allowed, while updateResource method will force any other thread to wait until the first one finishes.
Similarly, using volatile variable also helps in thread safety when you have multiple threads reading and writing common variable(s).
Example:
~~~ public class MyApp { public static volatile int counter = 0; public static void main(String args[]) throws InterruptedException { //we have created two threads t1 and t2 accessing increment() and decrement() method respectively Runnable r1 = () -> increment(); Runnable r2 = () -> decrement(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); t1.join(); t2.join(); //will print 0 System.out.println(“Counter value:” + counter); } public static void increment() { counter++; // can induce visibility problems in absence of volatile } public static void decrement() { counter–; // can induce visibility problems in absence of volatile } } ~~~
The counter variable increments and decrements in different threads so it must be declared volatile to ensure all threads will always see the same value.
Troubleshooting Tips for Using Java Volatile and Synchronized
Synchronizing your code is generally easier than volatile variables because all you need to do is add synchronization around certain sections of code. Volatile requires more changes such as changing access permissions of the fields being used by several threads.
In addition to this, opting for immutability (where variables cannot be changed) helps a great deal when writing concurrent applications as they are inherently thread-safe. A good example of an immutability design pattern is using immutable objects as map keys etc.
Conclusion
Volatile and synchronized are integral elements of Java language when it comes to developing multi-threaded applications. As we saw in this article, both keywords work on different levels – while volatile helps make values written by one thread visible to other threads, synchronized increases concurrency control around data sections that can potentially introduce race conditions.