This is the third day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

Thread-safe premise

When we talk about thread safety, we mainly talk about these aspects: atomicity, visibility, and orderliness. Let’s start with a few ideas.

Atomicity: An operation or a group of operations either all succeed or all fail. It’s kind of like sharing life and death. In multithreading, a group of operations can be broken, thus breaking atomicity.

Visibility: The operation of one thread is visible to another thread. The main point here is whether there will be dirty reads of shared variables.

Orderliness: means that programs are executed in the order in which we wrote the code. A lot of people here are like, what the hell? Not really. Remember when we talked about double retrieval in singleton mode, reordering would happen and it would be unsafe in a multithreaded environment. The JVM automatically optimizes the order in which our code is executed, but of course the end result is the same for a single thread.

So, in a multithreaded environment, we need atomicity, visibility, order. Meet the three, you can start the journey of thread safety. So back to today’s topic, what is volatile capable of, and can it make shared variable safety safe across multiple threads?

Conclusion: Volatile guarantees visibility and order, but not atomicity. So shared variables that are volatile may not be safe in a multithreaded environment.

How does volatile guarantee visibility

Let’s start with some pseudocode: here we define a stop variable in main, assign it a value of true, count thread 1 based on the stop loop, set stop to false for thread 2, and print the stop variable for thread 3. You’ll notice that false is printed but the while loop doesn’t stop. Why?

Stop = true thread1 int count; while(stop) { count++; } thread2 Thread.sleep(100); stop = false; thread3 Thread.sleep(200); log.info(stop); // falseCopy the code

We’ve often seen working memory models that use main memory and threads to explain this, and I’ve seen another interpretation, perhaps a more correct interpretation, that we’ll discuss.

Java has a just-in-time compiler that optimizes hot code, a block of code like a for loop or a while loop that is executed too frequently when the number of loops is greater than a certain value, and the JVM considers that piece of code to be hot code. To improve the efficiency of hot code execution, at runtime the virtual machine will compile the code into local platform-specific machine code and perform various levels of optimization, which is done by a Compiler called the Just In Time Compiler.

Due to JIT optimization, the stop in the while loop in Thread 2 is immediately considered true, and the while is not stopped even though it has been changed to false in main memory. Because the machine code in Thread 2 has not changed.

So how do you solve this problem? The JVM gives you the option to stop JIT optimization using the -xint configuration, but this would eliminate JIT for the entire project. It is better to use volatile to modify the stop parameter, which tells the virtual machine not to use JIT optimization stop. Because of this property, volatile ensures visibility.

How does volatile guarantee order

Volatile can ensure order by adding memory barriers. Here need to introduce another things, called happens-before principle, has eight principles, it is impossible to remember, I’m one of the examples, you feel a thread, according to the code sequence, in front of the first operation in writing on the back of the operation, uh, uh, that we don’t look very normal, The principle is that the JVM pays behind the scenes, but! Our JVM also does instruction reordering to optimize our code! A line of code may not only have one instruction, but it is this instruction rearrangement that turns our previously ordered code into an unordered instruction, so there may be a security problem.

Volatile ensures that instructions are safely ordered by adding memory barriers. In particular, it ensures that instructions are not reordered in front of or behind the barriers. That is, by the time the volatile keyword is executed, all operations preceding it are complete; It is also JIT – resistant and all changes are seen by all threads at the same time.

Volatile does not guarantee atomicity

As a trivial example, volatile can only modify one variable, and the operations I can perform on that variable may not themselves be atomic.

public volatile int count = 0; count ++; // The ++ operation itself is not atomicCopy the code

How is synchronized safe

Synchronized code blocks can only be executed by one thread at a time, which meets the atomicity, visibility and order of the code block as a whole.

conclusion

The main purpose of this article is to show that the well-known main memory and working memory models may not be an explanation of visibility, and hopefully today’s JIT explanation will give you some inspiration.