59 What Is the Memory Visibility Issue

59 What Is the Memory Visibility Issue #

In this lesson, we will mainly explain what the “visibility” problem is.

Let’s start with two examples to understand what the visibility problem is.

Example 1 #

Let’s take a look at the following code. There is a variable x, which is of type int:

public class Visibility {

    int x = 0;

    public void write() {

        x = 1;

    }

    public void read() {

        int y = x;

    }

}

This is a very simple code. The class has two methods:

  • The write method assigns a value to x. In the code, x is assigned the value 1. Since the initial value of x is 0, executing the write method changes the value of x.
  • The read method retrieves the value of x. When reading, we use a new variable y of type int to receive the value of x.

Let’s assume that two threads are executing the above code. The first thread executes the write method, and the second thread executes the read method. Now let’s analyze what happens during the actual execution of the code, as shown in the following diagram:

img

In the diagram, we can see that since the initial value of x is 0, both the first thread on the left and the second thread on the right can obtain this information from the main memory. For both threads, x is 0. However, let’s assume that the first thread executes the write method first. It changes the value of x from 0 to 1. But this change does not occur directly in the main memory. Instead, it occurs in the working memory of the first thread, as shown in the following diagram.

img

Now, let’s say that the working memory of thread 1 has not yet synchronized with the main memory. At this point, let’s say that thread 2 starts reading. It reads the value of x as 0, not 1. In other words, even though thread 1 has already changed the value of x, thread 2 is not aware of this change at all. This is what we call the visibility problem.

Example 2 #

Next, let’s look at another example. In the code below, there are two variables a and b, and they are assigned initial values of 10 and 20.

/**
 * Description: Demonstrates the problem caused by visibility
 */
public class VisibilityProblem {

    int a = 10;

    int b = 20;

    private void change() {

        a = 30;

        b = a;

    }

    private void print() {

        System.out.println("b=" + b + ";a=" + a);

    }

    public static void main(String[] args) {

        while (true) {

            VisibilityProblem problem = new VisibilityProblem();

            new Thread(new Runnable() {

                @Override
                public void run() {

                    try {
    ```

```java
Thread.sleep(1);
} catch (InterruptedException e) {
  e.printStackTrace();
}

problem.change();
}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

problem.print();

}

}).start();

}

}
}```

In the class, there are two methods:

  * `change` method, changes `a` to 30 and assigns the value of `a` to `b`.
  * `print` method, prints the value of `b` first, then prints the value of `a`.



Next, let's take a look at the `main` function. It is also very simple. First, there is an infinite `while` loop. In this loop, we create two threads and make them sleep for one millisecond before executing the `change` and `print` methods, respectively. The purpose of sleeping for one millisecond is to give them time to execute these two methods as close as possible.

Now let's run this code and analyze the possible scenarios.

  * Scenario 1: This is the most common scenario. Let's assume that the first thread, which executes the `change` method, runs first and completes, and then the second thread starts running. In this case, the second thread will naturally print the result `b = 30;a = 30`.
  * Scenario 2: The opposite of scenario 1. Just because a thread is started doesn't mean it will actually execute first. So in this scenario, the second thread prints first, and then the first thread executes the `change` method. Therefore, the printed value is the initial value of `a` and `b`, which is `b = 20;a = 10`.
  * Scenario 3: They run almost simultaneously, so there may be some overlap. For example, when the `change` method of the first thread is halfway through, with `a` already changed to 30, but `b` has not been modified yet, the second thread starts printing. So the value of `b` printed at this time is still the original value of 20, while `a` has already been changed to 30. Therefore, the printed result is `b = 20;a = 30`.

These scenarios are easy to understand, but there is one scenario that is not easy to understand: when the printed result is `b = 30;a = 10`. Let's think about why this happens:

  * First, the value printed is `b = 30`, which means that the value of `b` has been changed, meaning that the statement `b = a` has been executed.
  * If `b = a` is to be executed, then `a = 30` before it needs to be executed, so that `b` can be equal to the value of `a`, which is 30.
  * This also means that the `change` method has been completed.

However, in this scenario, if we print `a` again, the result should be `a = 30`, not the initial value of 10. Because during the execution of the `change` method, the value of `a` has been changed to 30, not the original value of 10. Therefore, if the scenario `b = 30;a = 10` occurs, it means that a visibility problem has occurred: the value of `a` has been modified by the first thread, but other threads cannot see it. Due to the fact that the latest value of `a` did not get synchronized in a timely manner, that's why the old value of `a` is printed. The probability of this happening is not high. I have attached a screenshot of the occurrence as an image for you to see:

![img](../images/Cgq2xl5zjgGAF-mdAABl3iL7a-k359.png)

### Solving the problem

So how can we avoid visibility issues? In case one, we can use the `volatile` keyword to solve the problem. We add the `volatile` modifier to the `x` variable in our original code, and leave the rest of the code unchanged. With the `volatile` keyword, as long as the first thread modifies the value of `x`, the second thread attempting to read `x` will always see the latest value, instead of an old value.

Similarly, in case two, if we add the `volatile` keyword to `a` and `b`, no matter how long the program runs, the scenario `b = 30;a = 10` will never occur. This is because `volatile` ensures that whenever the values of `a` and `b` change, the reading thread will always be able to perceive it.

### Measures to ensure visibility

Besides the `volatile` keyword, synchronized, Lock, and concurrent collections are a series of tools that can ensure visibility to some extent. The specific timing and means of ensuring visibility will be explained in detail in Lesson 61 on the happens-before principle.

#### Synchronized Ensures Both Atomicity and Visibility

Next, let's further analyze the `synchronized` keyword that we have used before. After understanding visibility issues, your understanding of `synchronized` will become even deeper.

There is a particularly important point about `synchronized` that we might not have considered before. We used to think that `synchronized` creates a critical section, where only one thread is allowed to perform operations at a time, thereby ensuring thread safety.

However, this understanding is not comprehensive, as it does not consider visibility issues. The complete statement is: `synchronized` not only ensures that at most one thread can execute operations inside the critical section at a time, but also guarantees that after the previous thread releases the lock, the next thread obtaining the same lock will see all the modifications made by the previous thread. This means that the reading thread must be able to read the latest values, because if the other threads cannot see the previous modifications, thread safety issues may still occur.

That's all for this lesson. In this lesson, we first presented two specific examples to illustrate what visibility issues are. Then, we introduced the methods to solve visibility issues, with the most commonly used one being the `volatile` keyword. Finally, we deepened our understanding of `synchronized` from the perspective of visibility.