As we all know, with more and more prosperous motherland, with the progress of science and technology, upgrading of equipment, computer architecture, operating systems, compiler is in constant reform and innovation, but one thing is always the same (I love of duck blood fans constancy) : that is the performance of the three time below: CPU < < memory I/O

But because of these changes, there are some weird problems with concurrent programs, the three most notorious of which are visibility, orderliness, and atomicity.

Today we’re going to focus on visibility among the three.

Zero, visibility elaboration

Visibility is defined as changes made by one thread to a shared variable that another thread can see immediately.

In the single-core era, all threads execute on the same CPU, so a write from one thread must be visible to other threads. It’s like having a general manager and a project manager.

At this point, the project manager sees the task G and assigns it to employee A and employee B. Then the progress of the task can be controlled by the project manager at any time. Every employee is informed of the latest project progress from the project manager.

In the multicore era, where each CPU has its own cache, visibility issues arise.

At this point, the two project managers check the task G at the same time and assign it to their subordinates respectively. Then the progress of this task can only be controlled by their project managers, because the progress of all employees is not reported to the same project manager. Therefore, each employee can only know the progress of their own project team staff, and not the progress of other project teams. Therefore, when multiple project managers do the same task, there may be a variety of problems, such as uneven task ratio, task progress delay, task repetition and so on.

To sum up the above examples, it is because the progress is not updated in time that the data is not up to date, leading to decision-making mistakes. So, you can vaguely see that memory doesn’t deal with the Cpu directly, but rather through caching.

CPU < -- > Cache < -- > memoryCopy the code

Represented by a picture is (multicore) :

Our explanations below, unless otherwise specified, are based on multicore.

Shared variables are not visible between threads.

Visibility problems are caused by Cpu cache inconsistencies for concurrent programming, and there are three main cases:

1.1. Thread cross execution

Thread cross execution is mostly caused by thread switching. For example, thread A in the following figure switches to thread B during execution and then switches back to thread A to perform the rest operations after completion of execution. In this case, thread B’s changes to the variable are not immediately visible to thread A, which results in A discrepancy between the calculated result and the desired result.

1.2. Reordering combined with thread cross execution

Take the following code for example

int a = 0; // line 1 int b = 0; // line 2 a = b + 10; // line 3 b = a + 9; / / line 4Copy the code

If lines 1 and 2 change order at compile time, the execution result is not affected;

If lines 3 and 4 were swapped when mutating, the execution result would be affected because the value of B is less than expected 19;

The result is inconsistent because the execution order is changed during compilation. The cross execution of the two threads leads to the result of the thread change is not the expected value, it is worse!

1.3 The updated value of the shared variable is not timely updated in the working memory and main memory

Because the master thread does not update the modification of the shared variable in time, the child thread cannot obtain the latest value immediately, causing the program cannot execute as expected.

For example, the following code:

package com.itquan.service.share.resources.controller; import java.time.LocalDateTime; /** * @author: mmzsblog * @description: */ public class VisibilityDemo {// State flag Private static Boolean flag =true;

    public static void main(String[] args) throws InterruptedException {
        System.out.println(LocalDateTime.now() + "Main thread start count child thread"); new CountThread().start(); Thread.sleep(1000); // Set flag tofalseCauses the child thread started above to jump outwhileLoop, end running visibilityDemo.flag =false;
        System.out.println(LocalDateTime.now() + "The main thread has set its status flag to false.");
    }

    static class CountThread extends Thread {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now() + "Count child thread start count");
            int i = 0;
            while (VisibilityDemo.flag) {
                i++;
            }
            System.out.println(LocalDateTime.now() + "Count child thread end count, end of run: the value of I is"+ i); }}}Copy the code

The results are as follows:

As can be seen from the printed result of the console, due to the modification of flag by the master thread, the counting child thread is not immediately visible, so the counting child thread cannot jump out of the while loop and end the child thread for a long time.

Welcome to pay attention to the public account “Java learning way “, check out more dry goods!

You can’t stand that, of course, which brings us to the next question: How do you solve inter-thread invisibility

How to solve the problem of invisibility between threads

To ensure visibility between threads we generally have three options:

2.1. Volatile: Only visibility is guaranteed

The volatile keyword guarantees visibility, but only visibility, where changes to the flag are immediately picked up by the counting child threads.

To correct the above example, simply add the volatile keyword to the global variable definition

// Status flag Private static volatile Boolean Flag =true;
Copy the code

2.2 Atomic Related classes: Ensure visibility and atomicity

The visibility and atomicity of flag attributes can be well guaranteed if the flag status is defined using Atomic related classes.

To correct the above example, simply define the global variables as Atomic related classes when you define them

Private static AtomicBoolean flag = new AtomicBoolean(true);
Copy the code

It is worth noting, however, that the method associated with the atomic class sets the new value and gets the value slightly differently, as follows:

Visibilitydemo.flag.set (visibilitydemo.flag.set (false); Visibilitydemo.flag.get () visibilityDemo.flag.get ()Copy the code

2.3 Lock: Ensure visibility and atomicity

Here we use the common Java synchronized keyword.

To correct the problem in the example above, simply add the synchronized keyword modifier to the count operation i++

    synchronized (this) {
        i++;
    }
Copy the code

In each of the above three ways, we can get an expected result similar to the following:

However, let’s take a closer look at the volatile and synchronized keywords. Welcome to pay attention to the public account “Java learning way “, check out more dry goods!

Visibility – Volatile

The Java memory model defines special access rules for the volatile keyword. When a variable is volatile, it has two properties, or volatile has two layers of semantics:

  • First, it ensures that the variable is visible when read by different threads. When one thread changes the value of a variable, the new value is immediately visible to other threads. (Volatile solves the visibility problem of shared variables between threads.)
  • Second, forbid instruction reordering to prevent the compiler from optimizing the code.

For the first point, volatile ensures that the variable is visible to different threads when they read it, as follows:

  • 1: Using the volatile keyword forces the value of a shared variable modified in a thread to be written to main memory immediately.
  • 2. Volatile changes made by thread 2 invalidate the working memory rows of variables in thread 1 (i.e., CPU L1 or L2 buffers).

Attached is a CPU cache model:

  • 3: The variable’s cache line in thread 1’s working memory is invalid, so thread 1 will read the variable’s value from main memory again. Because of this, we often see articles or books that say that volatile guarantees visibility.

Volatile variables cannot be read or written from the CPU cache, but must be read or written from memory.

Volatile is not thread-safe, so what does volatile do?

One of them :(marks the state quantity to ensure that the state quantity seen by other threads is the latest value)

The volatile keyword is the lightest synchronization mechanism that the Java virtual machine provides, and many people who don’t understand it well (see the happens-before principle here) often prefer synchronized.

4. Visibility synchronized

4.1. Scope

The scope of the synchronized keyword is as follows:

  • 1) Synchronized aMethod(){} is a synchronized method that prevents multiple threads from accessing the object at the same time.

    If an object has multiple synchronized methods, as long as one thread accesses one of the synchronized methods, no other thread can access any synchronized methods in the object at the same time.

    In this case, the synchronized methods of different object instances are irrelevant. That is, other threads can access synchronized methods in another object instance of the same class.

    Because when you modify a nonstatic method, you lock the current instance object.

  • Synchronized static aStaticMethod{} prevents multiple threads from accessing synchronized static methods in this class. It can act on all object instances of the class.

    Because when you modify a static method, you lock the Class object of the current Class.

Can be used in a block in a method

In addition to the synchronized keyword before a method, the synchronized keyword can also be used in a block of a method, indicating that only the resources in the block are mutually exclusive.

Usage is:

Synchronized (this){/* block */}Copy the code

Its scope is the current object;

4.3 Cannot inherit

The synchronized keyword is not inheritable, that is, the methods of the base class

synchronized f(){// specific operation}Copy the code

Not automatically in inherited classes

synchronized f(){// specific operation}Copy the code

It becomes

f(){// specific operation}Copy the code

An inherited class requires you to explicitly specify one of its methods as synchronized;

To sum up, synchronized keyword can be used in the following three ways:

  • Modifier instance method: locks the current instance before entering the synchronization code
  • Modifier static method: locks the current class object before entering the synchronization code
  • Modify code block: specifies the lock object, locks the given object, and obtains the lock for the given object before entering the synchronized code block

These three usages basically guarantee that when a shared variable is read, it reads the latest value.

4.4 Two JVM rules about synchronized:

  • The thread must flush the latest value of the shared variable to main memory before it can be unlocked

  • When a thread locks, the value of the shared variable in the working memory will be emptied. Therefore, when using the shared variable, it needs to read the latest value from the main memory (note: locking and unlocking are the same lock).

As you can also see from the above two rules, this ensures that the shared variable in memory must be the latest value.

However, the following points should be noted when using synchronized to ensure visibility:

  • A. Whether the synchronized keyword is added to a method or object, the lock it acquires is an object; Rather than treating a piece of code or a function as a lock — and synchronization methods are likely to be accessed by objects in other threads.
  • B. Each object has only one lock associated with it. The Java compiler automatically adds a lock() and unlock() to a synchronized modified method or block of code. The advantage of this is that unlock() must come in pairs, After all, forgetting to unlock() is a fatal Bug (meaning other threads have to wait).
  • C. Synchronization is expensive and can even cause deadlocks, so avoid unnecessary synchronization control.

So that’s my understanding and summary of visibility in union law, and next time we’ll talk about order in concurrency.

Reference article:

  • 1, Geek time Java concurrent programming combat
  • 2, www.jianshu.com/p/89a8fa8ff…
  • 3, www.cnblogs.com/xiaonantian…
  • 4, www.lagou.com/lgeduarticl…
  • 5, blog.csdn.net/evankaka/ar…
  • 6, juejin. Cn/post / 684490…


Welcome to the public account: The way of Java learning

Personal blog: www.mmzsblog.cn