Difference Between Volatile, Atomic And Synchronized in Java | Race Condition In Multi-Threading
Summary
TLDRThis video script delves into the intricacies of multithreading in Java, highlighting the challenges of race conditions when multiple threads access shared resources. It explains the use of 'synchronized', 'volatile', and 'atomic' to ensure thread safety, with examples to illustrate their application and the impact on data consistency. The script also clarifies the differences between these keywords and provides guidance on when to use each for optimal performance and reliability in concurrent programming scenarios.
Takeaways
- 🧵 Multithreading in Java allows for the simultaneous execution of two or more threads, which can lead to race conditions when shared resources are updated concurrently.
- 🔄 A race condition occurs when multiple threads access and modify a shared variable concurrently, leading to inconsistent and unpredictable results.
- 🔢 The script demonstrates a race condition with a simple example where two threads increment a shared variable, expecting a total of 30 but sometimes getting less due to concurrent access.
- 🔒 The 'synchronized' keyword in Java ensures that only one thread can access a method or block of code at a time for a given object, preventing race conditions by enforcing mutual exclusion.
- 🔄 Synchronization uses a locking mechanism where a thread must obtain a lock on an object before executing a synchronized method or block, causing other threads to wait.
- 🚫 Synchronized methods can lead to performance issues due to the overhead of lock management and have the potential for deadlocks and livelocks.
- 👀 The 'volatile' keyword in Java guarantees visibility of changes to a variable across threads, ensuring that a write to a volatile variable is immediately visible to other threads.
- 🔄 Volatile variables are useful for read/write operations but do not provide the atomicity of compound actions like incrementing a value.
- 🔢 'Atomic' variables from the java.util.concurrent.atomic package ensure that operations on them are performed atomically without the need for synchronization, providing a faster alternative for certain use cases.
- 🔄 Atomic variables use internal loops to retry actions if they are interrupted, ensuring that read-modify-write operations are completed without interference from other threads.
- ⚖️ Choosing between synchronized, volatile, and atomic depends on the specific needs of the multithreaded scenario, with each offering different trade-offs in terms of performance, simplicity, and safety.
Q & A
What is multithreading in Java?
-Multithreading is the process of executing two or more threads simultaneously, allowing for concurrent execution of tasks.
What is a race condition in multithreading?
-A race condition occurs when two or more threads try to update a mutually shared resource at the same time, potentially causing inconsistencies in data.
Can you explain the steps involved in the increment operation 'x = x + 1' in the context of a race condition?
-The increment operation 'x = x + 1' involves three steps: reading the value of x, adding one to this value, and storing the value back to x. A race condition can occur if two threads read the same value of x and then add one to it simultaneously, leading to incorrect data incrementation.
How does the 'synchronized' keyword solve race conditions?
-The 'synchronized' keyword ensures that only one thread can access a shared resource at a time by obtaining a lock on the object, making other threads wait until the lock is released.
What is cache incoherence and how does it relate to multithreading?
-Cache incoherence occurs when different threads or processes have different values of a variable in their respective caches, leading to inconsistent data. This can be resolved by using the 'volatile' keyword, which ensures that changes to a variable are immediately visible to all threads.
What is the 'volatile' keyword used for in Java?
-The 'volatile' keyword in Java is used to mark a variable as having its changes immediately visible to other threads, ensuring visibility and preventing cache incoherence.
How do atomic variables differ from synchronized methods or blocks?
-Atomic variables, such as AtomicInteger, guarantee that operations on the variable occur atomically, without interruption by other threads. They are part of the java.util.concurrent.atomic package and do not use locking mechanisms like synchronized methods or blocks.
Why might the performance of using atomic variables be faster compared to synchronized methods?
-Using atomic variables can be faster than synchronized methods because they do not involve the overhead of obtaining and releasing locks, and they are designed to perform operations atomically without locking.
What is the difference between 'volatile' and 'atomic' variables in terms of their use cases?
-Volatile variables are suitable for read and write operations where visibility is crucial, but no atomicity is required. Atomic variables are preferable for read-modify-write operations where both visibility and atomicity are necessary.
When should you use 'synchronized', 'volatile', or 'atomic' in multithreading?
-Use 'synchronized' for complete method or block synchronization when performance is not a critical concern. Use 'volatile' for visibility in read and write operations without the need for atomicity. Use 'atomic' variables for read-modify-write operations where atomicity is required.
Outlines
🧩 Introduction to Multithreading and Race Conditions
This paragraph introduces the concept of multithreading in Java, explaining it as the simultaneous execution of two or more threads. It discusses the potential for race conditions when threads try to update shared resources concurrently, leading to data inconsistencies. An example is given where two threads increment a shared integer variable, expecting a final value of 30, but due to race conditions, the result might be less. The paragraph also outlines the steps involved in a simple increment operation and how race conditions can occur within these steps.
🔒 Understanding Synchronization to Prevent Race Conditions
The second paragraph delves into synchronization as a solution to race conditions. It explains how the 'synchronized' keyword in Java can be used to ensure that only one thread accesses a shared resource at a time, thereby preventing inconsistencies. The paragraph provides an example of a race condition class with a shared 'count' variable, which is incremented by two threads. By making the method that increments 'count' synchronized, the program consistently outputs the expected value of 10, demonstrating the effectiveness of synchronization in maintaining data consistency.
🔑 The Role of Volatile for Visibility in Multithreading
This paragraph introduces the 'volatile' keyword in Java, which guarantees the visibility of changes made to a variable across threads. It addresses the issue of cache incoherence, where different threads may have different values of a variable in their caches, leading to inconsistencies. The paragraph provides an example of a 'VolatileExample' class with a 'stopRequested' variable that controls an infinite loop. Initially, the loop does not terminate even after 'stopRequested' is set to true due to cache incoherence. By declaring 'stopRequested' as volatile, the visibility of the variable is ensured, allowing the loop to terminate correctly.
⚛️ Atomic Variables for Safe Read-Modify-Write Operations
The fourth paragraph discusses atomic variables, which ensure that read-modify-write operations on a variable are performed atomically, without interruption from other threads. It introduces classes from the 'java.util.concurrent.atomic' package, such as 'AtomicInteger' and 'AtomicLong'. The paragraph revisits the race condition example, replacing the synchronized method with an atomic variable 'AtomicInteger' and using its 'getAndIncrement' method to ensure atomic operations. The example demonstrates that using atomic variables consistently results in the expected count of 10, regardless of the number of executions.
📊 Comparing Synchronized, Volatile, and Atomic in Java Multithreading
The final paragraph summarizes the differences between 'synchronized', 'volatile', and 'atomic' in the context of Java multithreading. It highlights that 'synchronized' is used for methods and blocks, while 'volatile' and 'atomic' are used for variables. The paragraph also contrasts the use cases: 'synchronized' for complete method or block synchronization, 'volatile' for read and write operations, and 'atomic' for read-modify-write operations. It discusses the performance implications of each approach and the potential for deadlock and livelock. The video concludes with guidance on when to use each method to resolve race conditions and ensure thread safety.
Mindmap
Keywords
💡Multithreading
💡Race Condition
💡Synchronization
💡Volatile
💡Atomic Variables
💡Mutual Exclusion
💡Cache Incoherence
💡Happens-Before Rule
💡Lock-Based Algorithms
💡Non-Lock Based Algorithms
💡Deadlock and Livelock
Highlights
Multithreading is the process of executing two or more threads simultaneously in Java.
Race conditions can cause inconsistencies in data when multiple threads update a shared resource concurrently.
The 'x = x + 1' operation involves three steps that can lead to race conditions if not properly managed.
A practical example demonstrates the occurrence of race conditions with a shared 'Count' variable.
The 'join' method ensures that threads complete execution before proceeding to the next instruction.
Adding sleep time in multiples of 100 for each iteration helps to visualize the effects of race conditions.
Synchronization prevents race conditions by allowing only one thread at a time to access a shared resource.
The 'synchronized' keyword can be applied to methods and blocks, ensuring mutual exclusion.
Cache incoherence occurs when processes read from and write to shared memory without proper synchronization.
The 'volatile' keyword ensures variable visibility across threads, preventing cache incoherence.
An example of a 'stopRequested' Boolean variable demonstrates the use of 'volatile' for visibility.
Atomic variables guarantee operations on a variable are performed atomically, without interruption.
Java's 'java.util.concurrent.atomic' package provides classes like AtomicInteger for atomic operations.
The 'getAndIncrement' method of AtomicInteger ensures atomic read-modify-write operations.
Atomic variables offer a performance advantage over synchronized methods due to the lack of locking.
The differences between synchronized, volatile, and atomic are summarized in terms of applicability and performance.
Guidance on when to use synchronized, volatile, or atomic based on the type of operations and performance considerations.
The video concludes with a comprehensive understanding of multithreading, race conditions, and the use of synchronization, volatile, and atomic variables.
Transcripts
Hi everyone, in this video we
are going to discuss about the difference between volatile, atomic and synchronized.
All these are related to the multithreading concept in Java and what is multithreading?
Multithreading is a process of executing two or more threads simultaneously.
And when we are executing two or more
threads when they're trying to update the
mutually shared resource at the same time, then a race condition might occur,
causing inconsistencies in data. For example, let's say for example,
we have int x equal to 10. And we wanted to loop it 10 times,
incrementing each time and let's say for example we have two threads executing this block of code.
So what's the expectation after these two threads execute this block of code
is x value should become 30, right? But we might not see the value as 30 every time,
but there can be there might be the value which we see below 30
and this is because even though we see the statement x equal x + 1 as a single statement.
Actually there are three steps involved in it. Reading the value of x. Adding one to this value and
storing the value back to x and these two threads can be at any of these substeps while executing,
and there is a chance of two threads reading the value of x at
the same time and adding one to the same value so that the actually the
the value should be incremented by two. But due to this problem it might
actually increment only once. This is what we call it as a race condition.
Let us actually see an example on this and understand what race condition is.
Let me create a class. I'll name my class as Race condition.
[no speech detected]
[no speech detected]
Let's take a variable. I'll take
my variable as integer Count.
And we wanted to this as thread program right?
So we wanted to create two threads, so I'll implement runnable interface here.
As soon as we include this, we need to implement the method run, so let's write some code.
Here I'll just increment my count variable by one each time and we wanted to have a loop, right?
So let's have a loop. I'll run it for only five times.
[no speech detected]
And let's create a main method. Where we are going to create an
object of this race condition class
And create two threads. I am going to create two threads T1.
[no speech detected]
Where will pass this race condition
Object to it, then we'll start this thread.
And let's create another thread.
I'll call this thread as t2. And then.
Will pass the same object to it.
I'm going to start this thread as well.
t2 start. When we say t1 start and t2
start right, then the run method gets invoked and this has to actually
run for five times for each thread. So finally the variable counts the
value of the count should be 10. So for that what we'll do is we'll add a method right?
A public method which will return the count.
I'll call this method as getCount.
Which actually returns the value of count.
And. Let's finally print
the count value here. We have the object right
As this is not static, so we'll use the object to call the. To actually access the count variable,
right. Now we wanted the count. Once the two threads gets executed,
like the two threads should die or the execution of threads should complete.
So for that we will use join method which will make sure that the next
instruction will be executed only upon the threads termination.
And we need to add the exception
to the method signature. Alright, and this method is going to run very fast and
we will not see the difference. So, what we'll do is I'm going to add sleep time.
[no speech detected]
In multiples of 100. For each iteration, right and.
Let's surround this with try catch block.
Alright. Let us execute the program now. So what did we do? We just created a count variable.
We wanted to increment it by one each time and we had a for loop which runs for
five times and we are having two threads. Each referencing to the same object, and two
threads should actually run in parallel. Executing this block of code which is
in present in the run method, right? And finally we should we are calling the getCount. We should call.
Sorry, we should call the get count method. To get the count right.
Finally, we are going to get the count, so the expectation is to get the count as 10, right?
So let's execute this program and verify. Now, if you observe here,
the count is 10 fine. Let's execute it again. And you can see the
value as nine this time. And let me execute it again
and this time also 9.
This time it is 7 see. If you observe. The same program when you are executing multiple times,
we are seeing different result right? So this is what we call it as data
inconsistency and this is happening because the value, right? Uh, if even though we are seeing
this as one increment operation, there are three steps involved in it. We just saw that right?
So at any point of time, the threads two threads might be reading the same value and incrementing
it to the next value, right? That's the reason we are seeing different
data each time when we run this program and let's go back. And how do we actually solve
this problem of race condition? Right?
We can make our method as synchronized. So what is synchronized
when we use synchronized keyword to our method or block then
it allows only single thread at a time to access the shared resource. Thereby it will not allow other
threads and make the other threads to wait for accessing the thread to
release the shared resource right? And how do how does this happens?
Like how does synchronized keyword or synchronization works
is each object in Java has a unique lock. And when a thread wants to execute
a shared resource, it actually obtains the lock on that object thereby avoiding other
threads to access the same object? And this synchronized can be
applied on methods and blocks. And it actually occurs on the object,
meaning only one thread can execute synchronous method or block at a
time for any given single object. So we can say that if we have two different threads executing a
synchronized method at that time, so they can actually execute on two different objects.
But on a single object only one thread can execute the synchronized method.
So this is very important. And this process of like at a
time only single thread accessing the shared resource, obtaining the lock and thereby a
other thread should be in waiting condition at that time and can only obtain the lock once the
current thread releases the lock. And this process is called as mutual exclusion.
Now let us go back to our example and. Add synchronized let's make
this method as synchronized. And how do we do that is we just need to add the keyword
synchronized to our method
[no speech detected]
And now we can see the value count 10 here. And we can execute this
program any number of times. You will always see the output as 10.
As now the method is synchronized. Meaning
The first thread right when it starts running, so this method gets called and as
we are making this run method as synchronized so the whole block will
be synchronized and t1 actually gets the lock on this whole method,
thereby actually making thread t2 wait. So only thread one after completing it,
execution only then t2 starts executing this synchronized block.
That's the reason we can actually see. The consistency of data, so any number of times you
execute this program, you will always see the count as 10. This is the use of synchronization.
Now let's go back.
And see what Cache incoherence is. Say for example we have main memory
we where we have a variable x value, 10 assigned to it and there is
a process P1 which is reading this value x and it will store it in its own cache
the value of x and say for example we have another process P2 which is also
reading the value x and actually writing. Increasing it by 10 and writing
back the value to the main memory. Now the actual value is 20,
but P1 has the value as 10 in its cache and will be using this value
in its evaluation in the program right, which might again lead to
the inconsistency of data. So how to actually solve? And this process is
called Cache incoherence. And how to solve this problem is we
can make this variable as volatile. Volatile is a keyword,
and when we have our variable as volatile, visibility is guaranteed meaning.
At any point of time, if one thread actually writes or modifies the value of a variable.
Then that is visible to the other threads. That visibility is guaranteed and volatile
also follows the happens before rule, meaning a write to a volatile field happens before every
subsequent read of that same field, so this is guaranteed, and this is the use of volatile.
Now let's see an example on volatile.
Let me create a new class. I'll call my classe as Volatile example.
[no speech detected]
Where OK, we wanted to implement the Runnable interface.
[no speech detected]
And what we'll do is we wanted to
implement the run method, right? Let's implement the run method here.
And. We wanted to actually let's create a Boolean variable.
I'll have this as static.
And name it, as stopRequested and we are going to have a while loop here.
Where it is going to run it till stopRequested is true.
And let's have a local variable i.
where it assigned a value at 0 right?
So it should start with zero and goes on increment, incrementing by one continuously so
infinite times until stopRequested is true. Stop requested. The default value of Boolean is false
so it enters into the loop and it always increments the value by one.
Now let's create the main method where we are going to create object for volatile example.
[no speech detected]
And create a thread. Where I'm
going to call this thread as Background thread, or yeah,
we'll just say background thread.
And pass volatileExample object.
Sorry it has to be thread new thread instance. Passing the
volatileExample object right. Now let us actually start the thread.
[no speech detected]
And we'll wait for some time,
maybe for one second and then.
We will actually modify that Boolean value to true.
[no speech detected]
Finally, we'll see. Main completed. This is just for our reference.
Will say Main completed right and once we are out of this loop we will just say.
We are done with. Background right back but this
is just we can say back end. Anything should be fine.
OK we called it as background thread, right? OK, let's call this as background thread completed.
Alright. So what did we do here? We are implementing runnable interface,
so we we wanted to implement a run method and we took a stopRequested Boolean
variable which runs forever like until the value stopRequested becomes true.
So we started here the run method right? And let's actually add the exception
to the main method signature. We are waiting for one one second and
then the main thread actually makes the
Boolean value stopRequested to true but. Uh, what happens here is,
so let let's execute the program and verify. So if I execute this program,
you can see it here. This is running forever, so it is still running this loop.
Actually, we started the thread background thread should be running and.
After that, so the main thread should continue its execution.
It should wait for 1 second and should modify the value as stopRequested to true, right?
And even though stopRequested is modified to true, this while loop is still executing.
So you can see it here. It is still executing. The program is still executed and main is completed,
but this is not completed. This is what we call it as cache incoherence. Stop requested,
has the value as false and it was not reading the value from the main memory as true, right?
So that's the reason it went to infinite loop. Now let's stop this program.
Make this boolean. Value to value as volatile and now
let me exceute the program. Again.
And this time you can see Main is completed and background thread is
also got completed because as volatile guarantees the visibility so stopRequested when the value is
modified to true it is available to this background thread as well. And this became true.
It came out of the loop. So this is how this is the use of volatile.
So whenever we wanted to have read and write operations in a multithreading environment so we can
make actually the variable as volatile and
remember volatile is not the replacement of synchronized method or block.
It can only be used for read write
operations. Now let's go back and. Volatile is done right now.
Let's see what atomic variable is atomic Variable guarantees that operations made on
the variable occurring in atomic fashion. Meaning even though we have like
a sub steps in a single step, all the steps gets completed
within the thread and they are not interrupted by any other threads.
This atomic variables are actually a classes and these classes comes from the
package java.util.concurrent.atomic. For example, we have atomic integer,
which can be declared it for INT values int variables.
For long variables, we can declare them as AtomicLong,
and so on.
And now let us see an example on atomic. We can take the same example of
our race condition, so let me copy this race condition example here.
To understand what atomic variable is.
We have it now.
Let's remove the synchronized. We wanted to declare our count as.
Atomic integer so we'll remove this as well.
And let's say this as atomic integer.
And how does atomic, uh, work is?
So we we can actually use count.
It is and it's actually an object now. So we should create an instance here
and then we can use the methods in it. getAndIncrement actually gets
the value and increment it by 1.
And the same program. We just need to
actually return the count here, right? So count dot We can say get here.
It's the count and this
is get an increment, right? At count is an atomic integer where
we are actually using a method getAndIncrement on the count which actually
gets the value and increment it by one, thereby assuring us the operation is atomic.
[no speech detected]
Now here we don't need any changes. Let us execute the program and verify.
[no speech detected]
And you can see the value as count 10. And you can verify it running
any number of times the data or the result will be consistent, will always see the count as 10,
and now I almost ran it four to five times. The result is always 10, right?
So this this is how, like, uh, we can actually make our variables
as atomic integers there by assuring the operations like read, modify, and write.
So these there are like 3 substeps in it right?
Read, modify and write. all of them.
as atomic meaning all are assured that the operation is atomic
And the getAndIncrement. The equivalent of this is how it
is executed internally is so it actually has a loop inside it.
So let let me show you that.
This is equivalent of a loop. So how does this atomic work is?
You can see here it actually reads the current value and compares it with the
with the current value, so it gets the value and compares
it with the current value and if it is successful it actually sets it,
sets the value or increment it by 1 right? And if it is not successful,
meaning the value is no longer equal to the current value,
then it actually reads and tries tries again.
So this is how Atomic variable works.
Now let's go back.
And see the differences, uh, between them? So synchronized it's applicable
on methods and blocks. Volatile is applicable on only variables.
Atomic is also applicable on only variables. Synchronized can be used to
implement lock based algorithms. Volatile can be used to implement non lock based algorithms.
Atomic is also there is no locking mechanism right, so we can use atomic to implement
the non lock based algorithms. Well synchronized performance will
be definitely slow because of obtaining and releasing of the locks.
Using Volatile the performance will be relatively faster than using
synchronized and using atomic variables. It will be more faster compared relatively faster compared
to synchronized and volatile. And synchronous there is no possibility of deadlock and livelock
as we always have synchronized using the locking mechanism right and there is always a possibility of
deadlock and livelock using Volatile and Atomic. these are the main differences and now.
The question when to use what, right? So when we wanted to have the
complete method or block to be like synchronized when we have like threads,
multiple threads like executing a method or block.
Then we can actually go for synchronized but compromising for the performance
as synchronized always has the locking mechanism and when we. When we are sure that we only have the
read and write operations like just now, we have seen the Boolean example right?
So in that case we can make our variable volatile. And if we have read,
modify and write operations, then it's better we go with the choice of atomic.
We can make our variable as atomic. So in this video we started with
understanding what multi threading is. What a race condition is and how to actually
resolve it using synchronized keyword, volatile and atomic and finally we
discussed the differences and when to use what. That's all for this video. Thanks for watching.
Weitere ähnliche Videos ansehen
L-3.4: Critical Section Problem | Mutual Exclusion, Progress and Bounded Waiting | Operating System
The Volatile Keyword in Java Explained with Example | Interview Question | Multithreading |
70+ Trickiest Core Java Interview Questions and Answers 😱
Learn English Grammar: The Adjective Clause (Relative Clause)
L-3.1: Process Synchronization | Process Types | Race Condition | Operating System-1
Chapter 4 - Introduction to Loop - Question Bank Solved Answers
5.0 / 5 (0 votes)