From the course: Advanced Java: Threads and Concurrency
Memory access in Java threads and its problems - Java Tutorial
From the course: Advanced Java: Threads and Concurrency
Memory access in Java threads and its problems
- [Instructor] A thread in computing is just like a person or a machine in the real world that can perform a task. When there are many of them, multiple tasks can be performed at the same time. So threads are what makes concurrency possible in Java. For instance, if we automate a pizza restaurant's kitchen, there'll be chefs assigned with the task of preparing the pizza dough, chefs assigned with the task of making the sauces, and some chefs assigned with the task of putting the toppings. The chefs here will be different threads. Now these threads share data and memory with other threads that are executing in parallel just as chefs in the kitchen will share kitchen utensils. Let's take a closer look at how Java threads access memory during execution. When we say memory, we are talking about RAM, or random access memory. According to the Java memory model, in RAM, there's the stack and the heap. You may already know that the stack memory holds primitive variables and reference variables, while the heap memory holds objects. A reference variable is a variable that points to an actual object in the heap that holds data and behavior. Each thread that's spawned by a program gets its own stack, or what's known as the thread stack. Thread stacks are private memory areas to the respective threads that own them. In contrast, the heap is a memory area shared by all the threads. Accordingly, the thread stack holds any local variables and reference variables created and used by the thread that owns the stack. The heap holds the actual objects that are referenced by reference variables. Let me quickly show you what this means. In my runnable, I've implemented a simple task to be executed by a thread. A runnable is an object that implements a task that can be executed by one or more threads. Then in the main class, I have a runnable instance, myRunnable1, created in line six, and I also see the thread class passing that runnable in line seven. If we take a closer look at how the variables and objects associated with this thread are stored in memory, the thread gets a runnable object. So this myRunnable1 reference variable over here is stored in the thread's thread stack while the object itself is stored in the heap. When executing the task, the thread creates a local variable i, and that's stored in its thread stack. Now, if I create a second runnable instance and another thread passing that runnable, there'll be another reference variable, myRunnable2, in the thread stack of thread2 while the object itself will be stored in the heap. This new thread2 will have its own copy of the local variable i, which will be stored in its own thread stack. This is how the variables and objects related to this code will be organized in memory. Now look at this code. If I create only one runnable instance and pass it to both the threads, then both threads are accessing and sharing the same runnable object, which is in the heap. However, they'll reference it using their own reference variables in their respective thread stacks. A snapshot of the memory arrangement looks like this. Now I'll show you what issues this memory model could cause in a multi-threaded program. Let's assume that I add a member variable counter in the runnable that will get incremented within the task code in the run method here. The task counts to one million. As both threads are sharing the same runnable object that contains the counter member variable, both will be incrementing the same counter, attempting to do it at the same time. Imagine what could happen. Well, two things could happen here. As both threads are sharing the same memory location, that's the counter, one thread could be reading the value of the counter while the other thread could be writing to it, possibly at the same time. This might result in threads reading stale data due to inconsistency in memory. This is known as a data race. Since there isn't any guarantee which thread will execute first, that is, the timing or ordering of thread execution, there could be an interference between threads. Then the computation of the counter might not happen as expected, especially when using a non-atomic operation like counter++. Here, we'd expect the counter to have a value of two million at the end of execution of both tasks. However, there's no guarantee that it will be so. This is known as a race condition. Understanding the Java memory model and its challenges is crucial for Java developers. It helps handle possible memory access issues in multi-threaded code and avoid them.
Contents
-
-
-
Memory access in Java threads and its problems5m 43s
-
(Locked)
Memory inconsistency: Data race5m 40s
-
(Locked)
Thread interference: Race condition5m 38s
-
(Locked)
Synchronization: Purpose and use3m 53s
-
(Locked)
Implementing synchronization3m 15s
-
(Locked)
Challenge: Inventory manager2m 45s
-
(Locked)
Solution: Inventory manager4m 50s
-
-
-
-
-
-
-
-