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

TechStack9
9 Jul 202123:32

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

00:00

🧩 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.

05:03

πŸ”’ 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.

10:08

πŸ”‘ 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.

15:08

βš›οΈ 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.

20:14

πŸ“Š 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

Multithreading refers to the concurrent execution of two or more threads within a computer program. It is a core concept in concurrent computing and is essential for improving the utilization of CPU resources. In the video, multithreading is introduced as the process of executing multiple threads simultaneously, which is central to understanding the need for synchronization mechanisms to prevent race conditions.

πŸ’‘Race Condition

A race condition occurs in software development when two or more threads access shared data and they try to change it at the same time. The script uses the example of incrementing a shared variable 'x' to illustrate how a race condition can lead to incorrect or inconsistent results, such as the final value of 'x' being less than the expected 30 after two threads have executed a loop ten times each.

πŸ’‘Synchronization

Synchronization is a technique used to control the access of multiple threads to shared resources in a multithreaded environment. The video explains that synchronization ensures that only one thread can access a method or block of code at a time by using the 'synchronized' keyword, which is crucial for maintaining data consistency and preventing race conditions.

πŸ’‘Volatile

The 'volatile' keyword in Java is used to indicate that a variable's value should always be read from the main memory, rather than from a thread's local cache. This ensures visibility of changes across threads, as demonstrated in the video with the 'stopRequested' variable, where making it volatile prevents cache incoherence and allows the background thread to see the updated value from the main thread.

πŸ’‘Atomic Variables

Atomic variables in Java are part of the java.util.concurrent.atomic package and are used to perform atomic operations on single variables without the need for synchronization. The video script discusses 'AtomicInteger' as an example, which guarantees that operations like 'getAndIncrement' are performed atomically, thus avoiding race conditions without the overhead of synchronization.

πŸ’‘Mutual Exclusion

Mutual exclusion is a principle in concurrent computing where a resource is accessed by only one thread at a time, ensuring that no two threads can enter a critical section simultaneously. The script explains that synchronization mechanisms like the 'synchronized' keyword enforce mutual exclusion, preventing multiple threads from executing a synchronized block of code at the same time.

πŸ’‘Cache Incoherence

Cache incoherence occurs when different processors or threads have different copies of the same data in their caches, leading to inconsistencies. The video script illustrates this with an example where process P1 reads a value from the main memory into its cache, while process P2 modifies and writes back the value to the main memory, resulting in P1 using stale data.

πŸ’‘Happens-Before Rule

The 'happens-before' rule in Java is a part of the Java Memory Model that defines the order in which actions of threads are observed to occur relative to each other. The video mentions that a write to a volatile field happens before every subsequent read of that same field, ensuring that changes made by one thread are visible to others.

πŸ’‘Lock-Based Algorithms

Lock-based algorithms are synchronization techniques that use locks to ensure that only one thread can access a resource at a time. The script explains that synchronized methods and blocks are examples of lock-based algorithms, which can be used to prevent race conditions but may lead to performance issues due to the overhead of lock management.

πŸ’‘Non-Lock Based Algorithms

Non-lock based algorithms are synchronization techniques that do not use traditional locking mechanisms but still ensure thread safety. The video script mentions that volatile variables and atomic variables can be used to implement non-lock based algorithms, which can offer better performance by avoiding the potential contention and overhead associated with locks.

πŸ’‘Deadlock and Livelock

Deadlock and livelock are concurrency issues that can occur in multithreaded programs. Deadlock happens when two or more threads are waiting indefinitely for resources held by each other, while livelock is a situation where threads are actively responding to each other in a way that prevents progress. The script notes that using synchronization can lead to the possibility of deadlock and livelock, whereas using volatile and atomic variables avoids these issues.

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

play00:00

Hi everyone, in this video we

play00:01

are going to discuss about the difference between volatile, atomic and synchronized.

play00:06

All these are related to the multithreading concept in Java and what is multithreading?

play00:12

Multithreading is a process of executing two or more threads simultaneously.

play00:17

And when we are executing two or more

play00:20

threads when they're trying to update the

play00:23

mutually shared resource at the same time, then a race condition might occur,

play00:29

causing inconsistencies in data. For example, let's say for example,

play00:34

we have int x equal to 10. And we wanted to loop it 10 times,

play00:41

incrementing each time and let's say for example we have two threads executing this block of code.

play00:47

So what's the expectation after these two threads execute this block of code

play00:52

is x value should become 30, right? But we might not see the value as 30 every time,

play00:59

but there can be there might be the value which we see below 30

play01:04

and this is because even though we see the statement x equal x + 1 as a single statement.

play01:11

Actually there are three steps involved in it. Reading the value of x. Adding one to this value and

play01:17

storing the value back to x and these two threads can be at any of these substeps while executing,

play01:23

and there is a chance of two threads reading the value of x at

play01:29

the same time and adding one to the same value so that the actually the

play01:34

the value should be incremented by two. But due to this problem it might

play01:40

actually increment only once. This is what we call it as a race condition.

play01:45

Let us actually see an example on this and understand what race condition is.

play01:52

Let me create a class. I'll name my class as Race condition.

play01:57

[no speech detected]

play02:00

[no speech detected]

play02:05

Let's take a variable. I'll take

play02:09

my variable as integer Count.

play02:12

And we wanted to this as thread program right?

play02:19

So we wanted to create two threads, so I'll implement runnable interface here.

play02:24

As soon as we include this, we need to implement the method run, so let's write some code.

play02:31

Here I'll just increment my count variable by one each time and we wanted to have a loop, right?

play02:37

So let's have a loop. I'll run it for only five times.

play02:43

[no speech detected]

play02:48

And let's create a main method. Where we are going to create an

play02:53

object of this race condition class

play02:58

And create two threads. I am going to create two threads T1.

play03:05

[no speech detected]

play03:08

Where will pass this race condition

play03:12

Object to it, then we'll start this thread.

play03:17

And let's create another thread.

play03:20

I'll call this thread as t2. And then.

play03:24

Will pass the same object to it.

play03:31

I'm going to start this thread as well.

play03:34

t2 start. When we say t1 start and t2

play03:39

start right, then the run method gets invoked and this has to actually

play03:44

run for five times for each thread. So finally the variable counts the

play03:49

value of the count should be 10. So for that what we'll do is we'll add a method right?

play03:55

A public method which will return the count.

play03:58

I'll call this method as getCount.

play04:02

Which actually returns the value of count.

play04:05

And. Let's finally print

play04:12

the count value here. We have the object right

play04:18

As this is not static, so we'll use the object to call the. To actually access the count variable,

play04:24

right. Now we wanted the count. Once the two threads gets executed,

play04:29

like the two threads should die or the execution of threads should complete.

play04:35

So for that we will use join method which will make sure that the next

play04:41

instruction will be executed only upon the threads termination.

play04:45

And we need to add the exception

play04:51

to the method signature. Alright, and this method is going to run very fast and

play04:56

we will not see the difference. So, what we'll do is I'm going to add sleep time.

play05:02

[no speech detected]

play05:07

In multiples of 100. For each iteration, right and.

play05:13

Let's surround this with try catch block.

play05:19

Alright. Let us execute the program now. So what did we do? We just created a count variable.

play05:26

We wanted to increment it by one each time and we had a for loop which runs for

play05:31

five times and we are having two threads. Each referencing to the same object, and two

play05:36

threads should actually run in parallel. Executing this block of code which is

play05:40

in present in the run method, right? And finally we should we are calling the getCount. We should call.

play05:46

Sorry, we should call the get count method. To get the count right.

play05:52

Finally, we are going to get the count, so the expectation is to get the count as 10, right?

play05:57

So let's execute this program and verify. Now, if you observe here,

play06:04

the count is 10 fine. Let's execute it again. And you can see the

play06:09

value as nine this time. And let me execute it again

play06:13

and this time also 9.

play06:17

This time it is 7 see. If you observe. The same program when you are executing multiple times,

play06:23

we are seeing different result right? So this is what we call it as data

play06:28

inconsistency and this is happening because the value, right? Uh, if even though we are seeing

play06:34

this as one increment operation, there are three steps involved in it. We just saw that right?

play06:40

So at any point of time, the threads two threads might be reading the same value and incrementing

play06:47

it to the next value, right? That's the reason we are seeing different

play06:52

data each time when we run this program and let's go back. And how do we actually solve

play06:59

this problem of race condition? Right?

play07:04

We can make our method as synchronized. So what is synchronized

play07:10

when we use synchronized keyword to our method or block then

play07:15

it allows only single thread at a time to access the shared resource. Thereby it will not allow other

play07:22

threads and make the other threads to wait for accessing the thread to

play07:27

release the shared resource right? And how do how does this happens?

play07:32

Like how does synchronized keyword or synchronization works

play07:35

is each object in Java has a unique lock. And when a thread wants to execute

play07:41

a shared resource, it actually obtains the lock on that object thereby avoiding other

play07:46

threads to access the same object? And this synchronized can be

play07:53

applied on methods and blocks. And it actually occurs on the object,

play07:58

meaning only one thread can execute synchronous method or block at a

play08:02

time for any given single object. So we can say that if we have two different threads executing a

play08:08

synchronized method at that time, so they can actually execute on two different objects.

play08:14

But on a single object only one thread can execute the synchronized method.

play08:18

So this is very important. And this process of like at a

play08:24

time only single thread accessing the shared resource, obtaining the lock and thereby a

play08:30

other thread should be in waiting condition at that time and can only obtain the lock once the

play08:37

current thread releases the lock. And this process is called as mutual exclusion.

play08:43

Now let us go back to our example and. Add synchronized let's make

play08:50

this method as synchronized. And how do we do that is we just need to add the keyword

play08:56

synchronized to our method

play08:57

[no speech detected]

play09:03

And now we can see the value count 10 here. And we can execute this

play09:09

program any number of times. You will always see the output as 10.

play09:13

As now the method is synchronized. Meaning

play09:18

The first thread right when it starts running, so this method gets called and as

play09:25

we are making this run method as synchronized so the whole block will

play09:30

be synchronized and t1 actually gets the lock on this whole method,

play09:36

thereby actually making thread t2 wait. So only thread one after completing it,

play09:41

execution only then t2 starts executing this synchronized block.

play09:46

That's the reason we can actually see. The consistency of data, so any number of times you

play09:52

execute this program, you will always see the count as 10. This is the use of synchronization.

play09:58

Now let's go back.

play10:02

And see what Cache incoherence is. Say for example we have main memory

play10:07

we where we have a variable x value, 10 assigned to it and there is

play10:13

a process P1 which is reading this value x and it will store it in its own cache

play10:19

the value of x and say for example we have another process P2 which is also

play10:25

reading the value x and actually writing. Increasing it by 10 and writing

play10:31

back the value to the main memory. Now the actual value is 20,

play10:36

but P1 has the value as 10 in its cache and will be using this value

play10:41

in its evaluation in the program right, which might again lead to

play10:46

the inconsistency of data. So how to actually solve? And this process is

play10:53

called Cache incoherence. And how to solve this problem is we

play10:57

can make this variable as volatile. Volatile is a keyword,

play11:02

and when we have our variable as volatile, visibility is guaranteed meaning.

play11:08

At any point of time, if one thread actually writes or modifies the value of a variable.

play11:15

Then that is visible to the other threads. That visibility is guaranteed and volatile

play11:21

also follows the happens before rule, meaning a write to a volatile field happens before every

play11:27

subsequent read of that same field, so this is guaranteed, and this is the use of volatile.

play11:33

Now let's see an example on volatile.

play11:39

Let me create a new class. I'll call my classe as Volatile example.

play11:45

[no speech detected]

play11:51

Where OK, we wanted to implement the Runnable interface.

play11:58

[no speech detected]

play12:01

And what we'll do is we wanted to

play12:06

implement the run method, right? Let's implement the run method here.

play12:10

And. We wanted to actually let's create a Boolean variable.

play12:16

I'll have this as static.

play12:22

And name it, as stopRequested and we are going to have a while loop here.

play12:28

Where it is going to run it till stopRequested is true.

play12:34

And let's have a local variable i.

play12:38

where it assigned a value at 0 right?

play12:45

So it should start with zero and goes on increment, incrementing by one continuously so

play12:50

infinite times until stopRequested is true. Stop requested. The default value of Boolean is false

play12:56

so it enters into the loop and it always increments the value by one.

play13:01

Now let's create the main method where we are going to create object for volatile example.

play13:06

[no speech detected]

play13:11

And create a thread. Where I'm

play13:15

going to call this thread as Background thread, or yeah,

play13:21

we'll just say background thread.

play13:27

And pass volatileExample object.

play13:34

Sorry it has to be thread new thread instance. Passing the

play13:39

volatileExample object right. Now let us actually start the thread.

play13:45

[no speech detected]

play13:48

And we'll wait for some time,

play13:51

maybe for one second and then.

play13:57

We will actually modify that Boolean value to true.

play14:02

[no speech detected]

play14:06

Finally, we'll see. Main completed. This is just for our reference.

play14:12

Will say Main completed right and once we are out of this loop we will just say.

play14:17

We are done with. Background right back but this

play14:24

is just we can say back end. Anything should be fine.

play14:29

OK we called it as background thread, right? OK, let's call this as background thread completed.

play14:35

Alright. So what did we do here? We are implementing runnable interface,

play14:41

so we we wanted to implement a run method and we took a stopRequested Boolean

play14:46

variable which runs forever like until the value stopRequested becomes true.

play14:51

So we started here the run method right? And let's actually add the exception

play14:55

to the main method signature. We are waiting for one one second and

play15:00

then the main thread actually makes the

play15:03

Boolean value stopRequested to true but. Uh, what happens here is,

play15:07

so let let's execute the program and verify. So if I execute this program,

play15:12

you can see it here. This is running forever, so it is still running this loop.

play15:19

Actually, we started the thread background thread should be running and.

play15:23

After that, so the main thread should continue its execution.

play15:27

It should wait for 1 second and should modify the value as stopRequested to true, right?

play15:32

And even though stopRequested is modified to true, this while loop is still executing.

play15:37

So you can see it here. It is still executing. The program is still executed and main is completed,

play15:43

but this is not completed. This is what we call it as cache incoherence. Stop requested,

play15:49

has the value as false and it was not reading the value from the main memory as true, right?

play15:55

So that's the reason it went to infinite loop. Now let's stop this program.

play16:01

Make this boolean. Value to value as volatile and now

play16:06

let me exceute the program. Again.

play16:12

And this time you can see Main is completed and background thread is

play16:16

also got completed because as volatile guarantees the visibility so stopRequested when the value is

play16:22

modified to true it is available to this background thread as well. And this became true.

play16:28

It came out of the loop. So this is how this is the use of volatile.

play16:34

So whenever we wanted to have read and write operations in a multithreading environment so we can

play16:40

make actually the variable as volatile and

play16:44

remember volatile is not the replacement of synchronized method or block.

play16:48

It can only be used for read write

play16:52

operations. Now let's go back and. Volatile is done right now.

play16:58

Let's see what atomic variable is atomic Variable guarantees that operations made on

play17:03

the variable occurring in atomic fashion. Meaning even though we have like

play17:07

a sub steps in a single step, all the steps gets completed

play17:12

within the thread and they are not interrupted by any other threads.

play17:17

This atomic variables are actually a classes and these classes comes from the

play17:24

package java.util.concurrent.atomic. For example, we have atomic integer,

play17:29

which can be declared it for INT values int variables.

play17:35

For long variables, we can declare them as AtomicLong,

play17:39

and so on.

play17:42

And now let us see an example on atomic. We can take the same example of

play17:48

our race condition, so let me copy this race condition example here.

play17:53

To understand what atomic variable is.

play17:57

We have it now.

play18:03

Let's remove the synchronized. We wanted to declare our count as.

play18:09

Atomic integer so we'll remove this as well.

play18:14

And let's say this as atomic integer.

play18:18

And how does atomic, uh, work is?

play18:22

So we we can actually use count.

play18:26

It is and it's actually an object now. So we should create an instance here

play18:32

and then we can use the methods in it. getAndIncrement actually gets

play18:37

the value and increment it by 1.

play18:43

And the same program. We just need to

play18:48

actually return the count here, right? So count dot We can say get here.

play18:54

It's the count and this

play19:00

is get an increment, right? At count is an atomic integer where

play19:06

we are actually using a method getAndIncrement on the count which actually

play19:11

gets the value and increment it by one, thereby assuring us the operation is atomic.

play19:17

[no speech detected]

play19:20

Now here we don't need any changes. Let us execute the program and verify.

play19:26

[no speech detected]

play19:30

And you can see the value as count 10. And you can verify it running

play19:36

any number of times the data or the result will be consistent, will always see the count as 10,

play19:42

and now I almost ran it four to five times. The result is always 10, right?

play19:47

So this this is how, like, uh, we can actually make our variables

play19:53

as atomic integers there by assuring the operations like read, modify, and write.

play19:58

So these there are like 3 substeps in it right?

play20:02

Read, modify and write. all of them.

play20:07

as atomic meaning all are assured that the operation is atomic

play20:13

And the getAndIncrement. The equivalent of this is how it

play20:20

is executed internally is so it actually has a loop inside it.

play20:26

So let let me show you that.

play20:30

This is equivalent of a loop. So how does this atomic work is?

play20:36

You can see here it actually reads the current value and compares it with the

play20:41

with the current value, so it gets the value and compares

play20:45

it with the current value and if it is successful it actually sets it,

play20:50

sets the value or increment it by 1 right? And if it is not successful,

play20:55

meaning the value is no longer equal to the current value,

play20:59

then it actually reads and tries tries again.

play21:02

So this is how Atomic variable works.

play21:08

Now let's go back.

play21:15

And see the differences, uh, between them? So synchronized it's applicable

play21:21

on methods and blocks. Volatile is applicable on only variables.

play21:25

Atomic is also applicable on only variables. Synchronized can be used to

play21:31

implement lock based algorithms. Volatile can be used to implement non lock based algorithms.

play21:36

Atomic is also there is no locking mechanism right, so we can use atomic to implement

play21:42

the non lock based algorithms. Well synchronized performance will

play21:47

be definitely slow because of obtaining and releasing of the locks.

play21:51

Using Volatile the performance will be relatively faster than using

play21:56

synchronized and using atomic variables. It will be more faster compared relatively faster compared

play22:02

to synchronized and volatile. And synchronous there is no possibility of deadlock and livelock

play22:09

as we always have synchronized using the locking mechanism right and there is always a possibility of

play22:16

deadlock and livelock using Volatile and Atomic. these are the main differences and now.

play22:22

The question when to use what, right? So when we wanted to have the

play22:28

complete method or block to be like synchronized when we have like threads,

play22:34

multiple threads like executing a method or block.

play22:38

Then we can actually go for synchronized but compromising for the performance

play22:43

as synchronized always has the locking mechanism and when we. When we are sure that we only have the

play22:50

read and write operations like just now, we have seen the Boolean example right?

play22:55

So in that case we can make our variable volatile. And if we have read,

play23:01

modify and write operations, then it's better we go with the choice of atomic.

play23:06

We can make our variable as atomic. So in this video we started with

play23:13

understanding what multi threading is. What a race condition is and how to actually

play23:18

resolve it using synchronized keyword, volatile and atomic and finally we

play23:24

discussed the differences and when to use what. That's all for this video. Thanks for watching.

Rate This
β˜…
β˜…
β˜…
β˜…
β˜…

5.0 / 5 (0 votes)

Related Tags
JavaMultithreadingRace ConditionSynchronizationVolatileAtomicThread SafetyProgrammingConcurrencySynchronized MethodsAtomic Variables