Java

Difference between volatile and synchronized in Java

25 September 2026 · 11 min read

Difference between volatile and synchronized in Java

In the realm of concurrent programming in Java, ensuring thread safety and proper data synchronization are paramount. Two keywords, volatile and synchronized, often emerge as key players in this domain. Understanding the nuances of their functionality and the scenarios where one excels over the other is crucial for crafting robust and efficient multithreaded applications. This post delves into the core differences between volatile and synchronized, providing clear examples and practical guidance on when to employ each. We’ll explore their impact on visibility, atomicity, and performance, empowering you to make informed decisions in your concurrent code.

Visibility and Ordering: The Core of volatile

The volatile keyword primarily addresses the visibility of shared variables across multiple threads. In a multi-core environment, each thread might cache a copy of a shared variable. Without volatile, modifications made by one thread to this variable might not be immediately reflected in the caches of other threads, leading to inconsistencies. Declaring a variable as volatile ensures that any write to that variable is immediately propagated to main memory, and any read reflects the latest value from main memory.

Furthermore, volatile enforces certain happens-before ordering guarantees. This ensures that any write operation preceding a volatile write in program order is visible to any read operation following the volatile read. This ordering is crucial for preventing unexpected behavior arising from compiler or hardware optimizations.

However, volatile does not guarantee atomicity. For operations involving multiple steps, such as incrementing a variable (read, modify, write), volatile alone is insufficient to ensure thread safety.

Mutual Exclusion with synchronized

The synchronized keyword, in contrast, establishes mutual exclusion. It allows only one thread at a time to access a particular block of code or an object, effectively preventing race conditions. This is achieved through the concept of intrinsic locks. Every object in Java has an associated intrinsic lock. When a thread enters a synchronized block, it acquires the lock associated with the specified object. Other threads attempting to enter the same synchronized block must wait until the lock is released.

Unlike volatile, synchronized guarantees both visibility and atomicity. All changes made within a synchronized block are visible to other threads once the lock is released, and compound operations within the block are executed atomically.

However, the use of synchronized introduces performance overhead. Acquiring and releasing locks can be relatively expensive, especially in highly concurrent scenarios.

Choosing the Right Tool for the Job

Selecting between volatile and synchronized hinges on the specific requirements of your concurrent code. If you need to ensure visibility of a single variable and do not require atomicity, volatile provides a lightweight solution. Conversely, when you need both visibility and atomicity for a block of code or an object, synchronized becomes essential.

Consider a scenario where you have a flag indicating the status of a process. If this flag is only ever updated by a single thread and only read by others, volatile suffices to ensure visibility. However, if multiple threads might concurrently attempt to modify the flag, synchronized is necessary to protect the update operation.

Practical Examples and Performance Considerations

Let’s illustrate with an example. Suppose you have a counter that needs to be incremented by multiple threads:

  • Using volatile alone for the counter would be incorrect as the increment operation is not atomic.
  • synchronized, however, would ensure correct counter updates.

Performance-wise, volatile generally has a lower overhead than synchronized. Excessive use of synchronized can lead to contention and reduced performance. Therefore, it’s essential to choose the least restrictive synchronization mechanism that meets your needs.

Java’s concurrency utilities offer more sophisticated tools like atomic variables and concurrent collections. These often provide better performance and scalability compared to basic synchronized blocks. Explore resources like Oracle’s Concurrency Tutorial for in-depth knowledge.

  1. Identify the shared resources in your code.
  2. Determine whether you need atomicity or just visibility.
  3. Choose between volatile, synchronized, or higher-level concurrency utilities based on your requirements.

Expert Quote: “Writing correct concurrent programs is notoriously difficult. The subtle interactions between threads can lead to unexpected and hard-to-debug errors.” - Brian Goetz, Java Language Architect at Oracle.

Infographic Placeholder: Visual comparison of volatile and synchronized.

Learn more about Concurrent Programming in Java.Featured Snippet: volatile ensures visibility of changes to a variable across threads, while synchronized provides mutual exclusion, preventing multiple threads from accessing a block of code simultaneously. Choose volatile for simple visibility guarantees and synchronized for atomicity and mutual exclusion.

FAQ

Q: Can volatile be used with methods?

A: No, volatile can only be applied to instance variables, not methods.

Q: Is synchronized always necessary for thread safety?

A: Not always. Other mechanisms like atomic variables and concurrent collections can provide thread safety without the overhead of synchronized.

In essence, volatile and synchronized serve distinct purposes in concurrent programming. By understanding their strengths and limitations, you can write robust and efficient multithreaded applications. Explore Java’s rich concurrency libraries and delve deeper into advanced synchronization techniques to further enhance your concurrent programming skills. This knowledge empowers you to confidently tackle the challenges of multithreading and build highly responsive and scalable applications. Consider diving deeper into the world of atomic variables and concurrent collections – they offer powerful tools for efficient and safe concurrent programming. Check out Baeldung’s guide on volatile and GeeksforGeeks’ explanation of synchronized for more detailed explanations.

Question & Answer :
I am wondering at the difference between declaring a variable as volatile and always accessing the variable in a synchronized(this) block in Java?

According to this article http://www.javamex.com/tutorials/synchronization_volatile.shtml there is a lot to be said and there are many differences but also some similarities.

I am particularly interested in this piece of info:

…

  • access to a volatile variable never has the potential to block: we’re only ever doing a simple read or write, so unlike a synchronized block we will never hold on to any lock;
  • because accessing a volatile variable never holds a lock, it is not suitable for cases where we want to read-update-write as an atomic operation (unless we’re prepared to “miss an update”);

What do they mean by read-update-write? Isn’t a write also an update or do they simply mean that the update is a write that depends on the read?

Most of all, when is it more suitable to declare variables volatile rather than access them through a synchronized block? Is it a good idea to use volatile for variables that depend on input? For instance, there is a variable called render that is read through the rendering loop and set by a keypress event?

It’s important to understand that there are two aspects to thread safety.

  1. execution control, and
  2. memory visibility

The first has to do with controlling when code executes (including the order in which instructions are executed) and whether it can execute concurrently, and the second to do with when the effects in memory of what has been done are visible to other threads. Because each CPU has several levels of cache between it and main memory, threads running on different CPUs or cores can see “memory” differently at any given moment in time because threads are permitted to obtain and work on private copies of main memory.

Using synchronized prevents any other thread from obtaining the monitor (or lock) for the same object, thereby preventing all code blocks protected by synchronization on the same object from executing concurrently. Synchronization also creates a “happens-before” memory barrier, causing a memory visibility constraint such that anything done up to the point some thread releases a lock appears to another thread subsequently acquiring the same lock to have happened before it acquired the lock. In practical terms, on current hardware, this typically causes flushing of the CPU caches when a monitor is acquired and writes to main memory when it is released, both of which are (relatively) expensive.

Using volatile, on the other hand, forces all accesses (read or write) to the volatile variable to occur to main memory, effectively keeping the volatile variable out of CPU caches. This can be useful for some actions where it is simply required that visibility of the variable be correct and order of accesses is not important. Using volatile also changes treatment of long and double to require accesses to them to be atomic; on some (older) hardware this might require locks, though not on modern 64 bit hardware. Under the new (JSR-133) memory model for Java 5+, the semantics of volatile have been strengthened to be almost as strong as synchronized with respect to memory visibility and instruction ordering (see http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#volatile). For the purposes of visibility, each access to a volatile field acts like half a synchronization.

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

-- JSR 133 (Java Memory Model) FAQ

So, now both forms of memory barrier (under the current JMM) cause an instruction re-ordering barrier which prevents the compiler or run-time from re-ordering instructions across the barrier. In the old JMM, volatile did not prevent re-ordering. This can be important, because apart from memory barriers the only limitation imposed is that, for any particular thread, the net effect of the code is the same as it would be if the instructions were executed in precisely the order in which they appear in the source.

One use of volatile is for a shared but immutable object which is recreated on the fly, with many other threads taking a reference to the object at a particular point in their execution cycle. One needs the other threads to begin using the recreated object once it is published, but does not need the additional overhead of full synchronization and it’s attendant contention and cache flushing.

// Declaration public class SharedLocation { static public volatile SomeObject someObject=new SomeObject(); // default object } // Publishing code SharedLocation.someObject=new SomeObject(...); // new object is published // Using code // Note: do not simply use SharedLocation.someObject.xxx(), since although // someObject will be internally consistent for xxx(), a subsequent // call to yyy() might be inconsistent with xxx() if the object was // replaced in between calls. private String getError() { SomeObject myCopy=SharedLocation.someObject; // gets current copy ... int cod=myCopy.getErrorCode(); String txt=myCopy.getErrorText(); return (cod+" - "+txt); } // And so on, with myCopy always in a consistent state within and across calls // Eventually we will return to the code that gets the current SomeObject. 

Speaking to your read-update-write question, specifically. Consider the following unsafe code:

public void updateCounter() { if(counter==1000) { counter=0; } else { counter++; } } 

Now, with the updateCounter() method unsynchronized, two threads may enter it at the same time. Among the many permutations of what could happen, one is that thread-1 does the test for counter==1000 and finds it true and is then suspended. Then thread-2 does the same test and also sees it true and is suspended. Then thread-1 resumes and sets counter to 0. Then thread-2 resumes and again sets counter to 0 because it missed the update from thread-1. This can also happen even if thread switching does not occur as I have described, but simply because two different cached copies of counter were present in two different CPU cores and the threads each ran on a separate core. For that matter, one thread could have counter at one value and the other could have counter at some entirely different value just because of caching.

What’s important in this example is that the variable counter was read from main memory into cache, updated in cache and only written back to main memory at some indeterminate point later when a memory barrier occurred or when the cache memory was needed for something else. Making the counter volatile is insufficient for thread-safety of this code, because the test for the maximum and the assignments are discrete operations, including the increment which is a set of non-atomic read+increment+write machine instructions, something like:

MOV EAX,counter INC EAX MOV counter,EAX 

Volatile variables are useful only when all operations performed on them are “atomic”, such as my example where a reference to a fully formed object is only read or written (and, indeed, typically it’s only written from a single point). Another example would be a volatile array reference backing a copy-on-write list, provided the array was only read by first taking a local copy of the reference to it.