Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to parse the volatile keyword from the root in Java concurrent programming

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Today, I will talk to you about how to parse the volatile keyword from the root in Java concurrent programming. Many people may not know much about it. In order to make you understand better, the editor summarizes the following content for you. I hope you can get something according to this article.

1. Parsing Overview

Related concepts of memory model

Three Concepts in concurrent programming

Java memory model

In-depth analysis of the volatile keyword

Scenarios using the volatile keyword

2. Related concepts of memory model.

Cache consistency issues. This kind of variable accessed by multiple threads is usually called a shared variable.

That is, if a variable is cached across multiple CPU (which usually occurs when programming with multiple threads), then there may be cache inconsistencies.

In order to solve the problem of cache inconsistency, there are generally two solutions:

By adding a LOCK# lock on the bus

Through the cache consistency protocol

Both methods are provided at the hardware level.

There is a problem with method 1 above, because other CPU cannot access memory while the bus is locked, resulting in inefficiency.

Cache consistency protocol. The most famous is Intel's MESI protocol, which ensures that copies of shared variables used in each cache are consistent. Its core idea is: when CPU writes data, if you find that the variable is a shared variable, that is, a copy of the variable also exists in other CPU, it will send a signal to other CPU to set the cache row of the variable into an invalid state, so when other CPU needs to read this variable, it will re-read it from memory.

3. Three concepts in concurrent programming

In concurrent programming, we usually encounter the following three problems: atomicity, visibility, and order.

3.1 atomicity

Atomicity: that is, the process in which one or more operations are either performed and executed will not be interrupted by any factor, or none of them will be performed.

3.2 visibility

Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can see the modified value immediately.

3.3 ordering

Orderliness: the order in which the program is executed according to the order of the code.

In terms of code order, statement 1 comes before statement 2, so does JVM guarantee that statement 1 will be executed before statement 2 when actually executing this code? Not necessarily. Why? Instruction reordering (Instruction Reorder) may occur here.

In general, the processor may optimize the input code in order to improve the efficiency of the program. It does not guarantee that the order of execution of each statement in the program is the same as that in the code, but it ensures that the final execution result of the program is consistent with that of the code sequence.

Instruction reordering does not affect the execution of a single thread, but it does affect the correctness of thread concurrent execution.

In other words, for concurrent programs to execute correctly, atomicity, visibility, and orderliness must be ensured. As long as one is not guaranteed, it may cause the program to run incorrectly.

4. Java memory model

In the Java virtual machine specification, we try to define a Java memory model (Java Memory Model,JMM) to shield the memory access differences of each hardware platform and operating system, so as to achieve the consistent memory access effect of Java programs on various platforms. So what does the Java memory model specify? it defines the access rules for variables in the program and, to a large extent, the order in which the program executes. Note that in order to achieve better execution performance, the Java memory model does not limit the execution engine to use the processor's registers or caches to speed up instruction execution, nor does it restrict the compiler to reorder instructions. In other words, there are cache consistency problems and instruction reordering problems in the java memory model.

The Java memory model states that all variables are stored in main memory (similar to the physical memory mentioned earlier), and each thread has its own working memory (similar to the previous cache). All operations on variables by a thread must be performed in working memory, not directly on main memory. And each thread cannot access the working memory of other threads.

4.1 atomicity

In Java, reading and assigning variables of basic data types are atomic operations, that is, these operations are uninterruptible and are either performed or not performed.

Analyze which of the following operations are atomic:

X = 10; / / statement 1

Y = x; / / statement 2

Xresume; / / statement 3

X = x + 1; / / statement 4

In fact, only statement 1 is an atomic operation, and the other three statements are not atomic operations.

In other words, only simple reading and assignment (and must assign numbers to a variable, and mutual assignment between variables is not an atomic operation) is an atomic operation.

As you can see from the above, the Java memory model only ensures that basic reads and assignments are atomic operations. If you want to achieve atomicity in a wider range of operations, you can do so through synchronized and Lock.

4.2 visibility

For visibility, Java provides the volatile keyword to ensure visibility.

When a shared variable is modified by volatile, it ensures that the modified value is immediately updated to main memory, and when another thread needs to read it, it reads the new value in memory.

On the other hand, ordinary shared variables can not guarantee visibility, because it is uncertain when ordinary shared variables will be written to main memory after they are modified, and when other threads read them, the old values in memory may still be the same, so visibility cannot be guaranteed.

In addition, visibility can also be guaranteed through synchronized and Lock, synchronized and Lock can ensure that only one thread acquires the lock and executes the synchronous code at a time, and flushes the changes to the variable to main memory before releasing the lock. So visibility can be guaranteed.

4.3 order

In the Java memory model, the compiler and processor are allowed to reorder instructions, but the reordering process does not affect the execution of single-threaded programs, but affects the correctness of multithreaded concurrent execution.

In Java, you can use the volatile keyword to ensure a certain degree of "orderability" (which prohibits instruction reordering). In addition, ordering can be ensured through synchronized and Lock. Obviously, synchronized and Lock guarantee that a thread executes synchronous code at every moment, which is equivalent to letting threads execute synchronous code sequentially.

In addition, the Java memory model has some innate "orderliness", that is, orderliness that can be guaranteed without any means, which is often referred to as the happens-before principle. If the execution order of the two operations cannot be deduced from the happens-before principle, then they cannot guarantee their order, and the virtual machine can reorder them at will.

The following is a specific introduction to the happens-before principle (the principle of first occurrence):

Program order rule: within a thread, according to the code order, the operation written in front occurs first in the operation written later.

Locking rule: a unLock operation occurs first on the same lock forehead lock operation.

Volatile variable rule: the write operation to a variable occurs first in the subsequent read operation on the variable

Transfer rule: if operation An occurs first in operation B, and operation B occurs in operation C first, it can be concluded that operation An occurs in operation C first.

Thread startup rule: the start () method of the Thread object occurs first in each action of this thread

Thread interrupt rule: the call to the thread interrupt () method occurs first in the code of the interrupted thread to detect the occurrence of the interrupt event

Thread termination rule: all operations in a thread first occur in the termination detection of the thread. We can detect that the thread has terminated execution through the termination of the Thread.join () method and the return value of Thread.isAlive ().

Object termination rule: the initialization of an object occurs first at the beginning of its finalize () method

Among the eight rules, the first four rules are more important, and the last four rules are obvious.

Let's explain the first four rules:

As far as program order rules are concerned, my understanding is that the execution of a piece of program code appears to be orderly in a single thread. Note that although it is mentioned in this rule that "the operation written in front occurs first in the operation written later," this should be the order in which the program appears to be executed in code order. because the virtual machine may reorder the program code. Although reordering is carried out, the final result of execution is consistent with the sequential execution of the program, which only reorders instructions that do not have data dependencies. Therefore, it is important to understand that program execution appears to be orderly in a single thread. In fact, this rule is used to ensure the correctness of the execution result of the program in a single thread, but it cannot guarantee the correctness of the execution of the program in multi-thread.

The second rule is also relatively easy to understand, that is, if the same lock is in a locked state, either in a single thread or in a multithread, the lock must be released before the lock operation can proceed.

The third rule is a more important rule, and it is also the content that will be emphasized later. The intuitive explanation is that if a thread writes a variable first and then a thread reads it, then the write operation must occur first in the read operation.

The fourth rule actually embodies the transitivity of the happens-before principle.

5. In-depth analysis of volatile keyword

5.1 two-layer semantics of Volatile keywords

Once a shared variable (member variable of a class, static member variable of a class) is modified by volatile, there are two layers of semantics:

It ensures the visibility of different threads when operating on this variable, that is, one thread modifies the value of a variable, and the new value is immediately visible to other threads.

Instruction reordering is prohibited.

For visibility, take a look at a piece of code. If thread 1 executes first, thread 2 executes later:

/ / Thread 1 boolean stop = false; while (! stop) {doSomething ();} / Thread 2 stop = true

This code is a typical piece of code, and many people may use this marking method when interrupting threads. But in fact, will this code work completely correctly? That is, will the thread be interrupted? Not necessarily, perhaps most of the time, this code can interrupt the thread, but it is also possible that the thread cannot be interrupted (although this is unlikely, as long as this happens, it will cause a dead loop).

Let's explain why this code can cause threads to be uninterruptible. As explained earlier, each thread has its own working memory while running, so thread 1 will put a copy of the value of the stop variable in its own working memory at run time.

So when thread 2 changes the value of the stop variable, but before it has time to write to main memory, thread 2 goes on to do something else, so thread 1 will continue to loop because it does not know the change of thread 2 to the stop variable.

But after decorating with volatile, it becomes different:

*: use the volatile keyword to force the modified value to be written to main memory immediately

Second: if you use the volatile keyword, when thread 2 modifies, the cache line of the cache variable stop 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)

Third: because the cache line of the cache variable stop in thread 1's working memory is invalid, thread 1 will go to main memory to read the value of the variable stop again.

Then when thread 2 modifies the stopvalue (of course, there are two operations here, modify the value in thread 2's working memory, and then write the modified value into memory), it will invalidate the cache line of the cache variable stop in thread 1's working memory, and then thread 1 will find its cache line invalid when it reads it. It will wait for the main memory address corresponding to the cache line to be updated, and then go to the corresponding main memory to read the value of * *.

Then thread 1 reads the correct value of *.

5.2 does volatile guarantee atomicity?

Volatile does not guarantee atomicity, so let's look at an example.

Public class Test {public volatile int inc = 0; public void increase () {inc++;} public static void main (String [] args) {final Test test = new Test ()

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.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report