In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-01 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the knowledge of "how to understand Java concurrency visibility". Many people will encounter this dilemma in the operation of actual cases, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
01 elaboration of visibility
Visibility is defined as a modification made by one thread to a shared variable that the other thread can see immediately.
In a single-core era, all threads execute on a single CPU, so the writing of one thread must be visible to other threads. For example, a general manager has only one project manager.
At this point, after the project manager sees the task G, it is assigned to employee An and employee B, then the progress of this task can be controlled by the project manager at any time; each employee can know the latest project progress from the project manager.
In the multi-core era, each CPU has its own cache, which has a visibility problem.
At this point, after the two project managers see task G at the same time, they each assign it to their subordinate employees, so the progress of this task can only be controlled by their respective project managers, because the work progress of all employees is not reported to the same project manager; then, each employee can only know the work progress of his own project team staff, not the work progress of other project teams. Therefore, when multiple project managers are doing the same task, there may be many problems, such as uneven task matching, task schedule delay, task repetition and so on.
Summing up the above example, it is because the progress is not updated in time, resulting in the data is not up-to-date, resulting in mistakes in decision-making. So, we can vaguely see that memory does not deal directly with Cpu, but with Cpu through caching.
Cpu cache memory
Represented by a picture, it is (multicore):
What we describe below, unless otherwise specified, is based on multicore.
02 cause analysis
Visibility problems are caused by Cpu cache inconsistencies for concurrent programming, and there are three main situations:
2.1. Thread cross execution
Most of the cross-execution of threads is caused by thread switching, for example, thread An in the following figure switches to thread B after the execution is completed, and then switches back to thread A to perform the rest of the operation; at this time, the modification of variables by thread B can not be immediately visible to thread A, which leads to the inconsistency between the calculation results and the ideal results.
2.2. Reordering combined with thread cross execution
For example, the following code
Int a = 0; / / Line 1 int b = 0; / / Line 2 a = b + 10; / / Line 3 b = a + 9; / / Line 4
If lines 1 and 2 change the order at compile time, the execution results will not be affected
If rows 3 and 4 are swapped in the order of variation, the execution result will be affected because b is worth less than the expected 19.
As can be seen from the diagram: the result is inconsistent due to the change of the execution order at compile time, and the cross execution of the two threads leads to the result that the changed thread is not the expected value, which is even worse!
2.3. The updated values of shared variables are not updated in time between working memory and main memory.
Because the modification of the shared variable by the main thread is not updated in time, the child thread can not get the latest value immediately, so the program can not execute according to the expected result.
For example, the following code:
Package com.itquan.service.share.resources.controller; import java.time.LocalDateTime; / * * @ author: mmzsblog * @ description: visibility test of shared variables between threads * / public class VisibilityDemo {/ / status identification flag private static boolean flag = true; public static void main (String [] args) throws InterruptedException {System.out.println (LocalDateTime.now () + "main thread startup count child thread") New CountThread (). Start (); Thread.sleep (1000); / / set flag to false so that the child thread started above jumps out of the while loop and ends running VisibilityDemo.flag = false; System.out.println (LocalDateTime.now () + "the main thread sets the status identification 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) {iThread + } System.out.println (LocalDateTime.now () + "count child thread end count, run end: the value of I is" + I);}
The result of the operation is:
As can be seen from the print results of the console, because of the modification of the main thread to flag, the counting child thread is not immediately visible, so the counting child thread can not jump out of the while cycle for a long time and end the child thread.
As a fan with obsessive-compulsive disorder, I certainly can't stand this situation, so it leads to the next question: how to solve the invisibility between threads.
03 how to solve the invisibility between threads
We generally have three options to ensure inter-thread visibility:
3. 1. Volatile: only ensure visibility
The volatile keyword guarantees visibility, but only visibility, and here you can ensure that changes to the flag can be immediately obtained by the counting child thread.
To correct the problem in the above example, just add the volatile keyword when defining the global variable
/ / status identification flag
Private static volatile boolean flag = true
Atomic related classes: ensure visibility and atomicity
If the identity state flag is defined using Atomic-related classes, the visibility and atomicity of the flag attribute can be well guaranteed.
To correct the problem in the above example, you only need to define the variable as an Atomic-related class when defining the global variable.
/ / status identification flag
Private static AtomicBoolean flag = new AtomicBoolean (true)
However, it is worth noting that at this time, the atomic class-related methods set new values and get values that change a bit, as follows:
/ / set the value of flag VisibilityDemo.flag.set (false); / / get the value of flag VisibilityDemo.flag.get ()
Lock: ensure visibility and atomicity
Here we are using the synchronized keyword that is common to Java.
To correct the problem in the above example, you only need to add the synchronized keyword modifier for the counting operation icount +.
Synchronized (this) {iTunes;}
Through the above three ways, I get similar expected results as follows:
However, next, I would like to explain the volatile and synchronized keywords in more detail.
04 visibility-volatile
The Java memory model defines some special access rules for the volatile keyword. When a variable is modified by volatile, it will have two characteristics, or volatile has the following two layers of semantics:
First, it ensures the visibility of different threads when reading this variable. That is, a thread modifies the value of a variable, and the new value is immediately visible to other threads. Volatile solves the problem of visibility of shared variables between threads.
Second, instruction reordering is prohibited to prevent the compiler from optimizing the code.
For the first point, volatile ensures the visibility of different threads when reading this variable, as follows:
1: use the volatile keyword to force the value of a shared variable modified in a thread to be written to main memory immediately.
2: if you use the volatile keyword, when thread 2 modifies, the cache line of the variable in the working memory of thread 1 will be invalid (if reflected in the hardware layer, the corresponding cache line in the L1 or L2 cache of CPU is invalid)
Attached is a diagram of the CPU cache model:
3: because the cache line of the variable in thread 1's working memory is invalid, thread 1 will go to main memory to read the value of the variable again. Based on this, we often see articles or books saying that volatile ensures visibility.
To sum up: it is a variable modified with volatile. Reading and writing to this variable cannot use CPU cache, but must be read or written from memory.
Using volatile does not guarantee thread safety, so what is the purpose of volatile?
One of them: (Mark 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 provided by the Java virtual machine, and many people prefer to use synchronized for synchronization because they don't understand it enough (in fact, take a look at the happens-before principle if you want to understand it here). So next, Fan, let me talk about the synchronized keyword.
05 visibility synchronized
5.1, scope
The synchronized keyword has two scopes:
1) within an object instance, synchronized aMethod () {} prevents multiple threads from simultaneously accessing the object's synchronized method.
If an object has multiple synchronized methods, as long as one thread accesses one of the synchronized methods, no other thread can access any of the synchronized methods in the object at the same time.
At this point, the synchronized methods of different object instances do not interfere with each other. That is, other threads can access the synchronized method in another object instance of the same kind at the same time.
Because when decorating a non-static method, the current instance object is locked.
2) is the scope of a class, and synchronized static aStaticMethod {} prevents multiple threads from accessing the synchronized static method in this class at the same time. It works on all object instances of the class.
Because when you decorate a static method, you lock the Class object of the current class.
5.2. Can be used in a block in the method
In addition to using the synchronized keyword before the method, the synchronized keyword can also be used in a block in the method to indicate exclusive access to only the resources in that block.
The usage is:
Synchronized (this) {/ * Block * /}
Its scope is the current object
5.3. Cannot inherit
The synchronized keyword cannot be inherited, that is, the methods of the base class
Synchronized f () {/ / specific operation}
Is not automatically in an inherited class
Synchronized f () {/ / specific operation}
It became...
F () {/ / specific operation}
Inheriting a class requires you to explicitly specify one of its methods as a synchronized method.
To sum up, there are three main uses of the synchronized keyword:
Modify the instance method: lock the current instance, and obtain the lock of the current instance before entering the synchronization code
Modify static method: lock the current class object, and obtain the lock of the current class object before entering the synchronization code
Decorated code block: specify a locked object, lock a given object, and obtain the lock of a given object before entering the synchronous code block
These three uses basically ensure that when a shared variable is read, it reads the latest value.
JVM's two provisions on synchronized:
Before the thread is unlocked, the latest value of the shared variable must be flushed to the main memory
When a thread is locked, it clears the value of the shared variable in the working memory, so when using the shared variable, you need to re-read the latest value from the main memory (note: locking and unlocking are the same lock)
As you can see from the above two rules, this approach ensures that shared variables in memory must be up-to-date.
However, we should also pay attention to the following points when using synchronized to ensure visibility:
a. Whether the synchronized keyword is added to a method or an object, the lock it acquires is an object; instead of treating a piece of code or function as a lock-- and the synchronization method is likely to be accessed by objects from other threads.
b. Each object has only one lock associated with it. The Java compiler will automatically add lock lock () and unlock unlock () before and after synchronized-decorated methods or blocks of code. The advantage of this is that locking lock () and unlocking unlock () must occur in pairs. After all, forgetting to unlock unlock () is a deadly Bug (meaning that other threads will have to wait).
c. The realization of synchronization takes a lot of system overhead as a price, and may even cause deadlock, so try to avoid unnecessary synchronization control.
That's all for "how to understand Java concurrency visibility". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.