Difference Between Volatile, Atomic And Synchronized in Java | Race Condition In Multi-Threading

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: TechStack9
Views: 16,007
Rating: undefined out of 5
Keywords: Java, Tricky, Interview, Questions, interview, method, class, javaprogramming, coding, java, javadeveloper, programming, programmer, programminglife, programminglanguage, backenddeveloper, backend, developer, interviewchallenge, interviewquestions, interviewpreparation, codinginterview, interviewskills, interviews, Multi-threading, Race condition, Synchronized, Volatile, Atomic, Cache incoherence, Main memory, process, threads, lock
Id: 3vptroRUvn8
Channel Id: undefined
Length: 23min 32sec (1412 seconds)
Published: Fri Jul 09 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.