As we all know, programming is a metaphysics.
This article describes the impact of output statements, sleep, and Integer on thread safety. I first encountered this problem 122 days ago and thought it was strange.
Why Integer? I don’t know, maybe metaphysics! This is also a question left at the end of this article, if there are friends who know, please give advice or two.
Talk about life
First of all, or the characteristics of this number, first talk about life.
The picture above was taken by me in The Xishan National Forest Park in Beijing on December 9, 2017.
The place where the photos were taken has a funny name: The Ghost Laughing Stone.
I stayed in Beijing for three years, I only went to this place twice in total, this is the first time to go to the photo, I went from the Fragrant Hill to the West hill, at that time was still a full of fighting north drift.
The second time I went was because I felt I might be leaving Beijing. If there was one place I could linger on before I left, Ghost Laughing Stone was one of them. So I invited several friends to climb again.
At a glance in this place, you can stand on the edge of the fifth ring Road and see most of Beijing, from the sunset, birds returning to the forest to see the lights on, thousands of lights.
You can feel your own insignificance in such a big Beijing, and also feel that in such a big Beijing, you must work hard to live up to the time of drifting north.
Both times I listened to the same song, Zhao Lei’s “Ideal” :
I slept past the station on the bus
All the way I looked at the neon Beijing
My ideal threw me in this crowded crowd
Out of the window was a blanket of snow
.
How old are you this year
You're always seducing your younger friends
You always thank me and surprise me
Let me sink into a life of disappointment
.
An ideal is always young
You made me rebel against fate
You make me pale
But still naive to believe that flowers will bloom again
Copy the code
The lyrics are really good, Zhao Lei sings really good, so that every time I hear this song in the future, I will think of those days when north drift.
Every time a reader tells me in private, he’s starting to drift north. I would say: must cherish, grasp, do not waste every day north drift.
All right, back to the article.
This article describes the impact of output statements, sleep, and Integer on thread safety.
Why Integer? I don’t know, maybe metaphysics!
First out a problem
This program defines a Boolean flag and sets it to false. The main thread loops until flag is true.
When does flag change to true?
Set the flag to true after the child thread sleeps for 100ms.
Here, do you think this program will end normally?
But anyone who has a certain foundation in Java concurrent programming can see that this program is an endless loop. The reason for the loop is that flag variables are not volatile, so changes to flag by child threads may not be visible to the main thread.
This is definitely not seen by the main thread if the application is running in Server mode in HotSpot JVM, for reasons explained later.
If you’re not sure about the Java memory model and the role of the volatile keyword, I suggest you do some research on the topic before you read this article.
Since the Java memory model and the volatile keyword come up so frequently in interviews, many articles have been written about them that this article will not explain these basic concepts.
I assume that you understand the Java memory model and the role of the volatile keyword.
I first encountered this problem on November 19, 2019, 122 days after today. I often think of this problem and its variations at night, why? Why on earth?
I’m going to give you a copy and paste version that will run directly. I suggest that you execute all the code in this article, and you will know: MD, this is a great thing!
public class VolatileExample { private static boolean flag = false; private static int i = 0; public static void main(String[] args) { new Thread(() -> { try { TimeUnit.MILLISECONDS.sleep(100); flag = true; System.out.println("flag changed to true"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); while (! flag) { i++; } system.out. println(" end of program, I =" + I); }Copy the code
}
Also, it should be noted that the normal operation for a program to end as expected is to volatile the flag variable. But volatile would be meaningless, and would be meaningless to explore.
So these SAO operations below, just do research, real scenes can not do so.
In addition, it is important to note that different machines, different JVMS, and different cpus may produce different effects.
Up in the air quantum mechanics
I will make three very small changes in this section based on the program shown above.
Trust me, it’s totally confusing. It even makes you think: Impossible? I have to do it myself.
And then you say, oh my God, is that true? Is this quantum mechanics?
The first program modification
So let me change this to the following:
Just add an output statement at line 24 to print the value of flag for each loop. Nothing else has changed.
It can be seen that idea also gives us a friendly reminder in line 24:
It says: Flag is always false.
Here, guess again. Is this program still in an infinite loop?
After you execute it, the program ends normally, but you don’t know why, and you just shout, “Oh, my God!
Or you say you know because the output contains the synchronized keyword.
Good. Don’t worry. Keep reading. See if I get hit in the face.
The second program modification
Take a look at the following program:
The change was to add a 10ms of sleep to the while loop.
Here, guess again. Is this program still in an infinite loop?
After you execute it, the program ends normally, but you don’t know why, and you just shout again, “Oh my God, this is amazing!”
“Sleep” is not synchronized. Explain that to me again.
You might say, I know that, sleep causes a refresh of memory.
Here, stick the other side of your face in and get hit.
The third program transformation
Now look at this transformation program:
The change this time is in line 9, where the variable I is volatile. Note that the flag variable is not volatile.
In line 23, IDEA gives another friendly reminder:
A nonatomic operation was performed on the volatile field I.
But that’s okay, guys, that’s not the point of the question, okay?
All you need to know is that I ++ is not an option on volatile variable I, because volatile guarantees visibility, not atomicity, and I ++ is not atomic.
Here, guess again. Is the above program still in an endless loop?
After you execute it, the program ends normally, but you still don’t know why, and you can only shout again, “Oh, my God!
The fourth program transformation
Look at the final transformation, the transformation of the final blow:
Again, in line 9, we change the variable I from the base int type to the wrapper type Integer.
Come on, guess again…
Forget it, don’t guess, just shout:
The program will also end normally.
The above four cases, you taste, how do you explain.
Effective Java
In fact, Effective Java, the Holy book of Java, also mentions this problem.
In section 66 (Synchronous Access to Shared mutable Data), there is a procedure like this:
How do you think this program will work?
You might expect this program to run for about a second, after which the main thread sets stopRequested to true, causing the background thread to stop the loop. But on my machine, the program never terminates: background threads are always looping!
The problem is that because there is no synchronization, there is no guarantee that background threads will “see” changes made to stopRequested values by the main thread.
There is no synchronization, so the virtual machine will change the code to look like this:
Here’s what it says:
The concept of activity failure is mentioned in the book: in multi-linear concurrency, if thread A modifs the shared variable, thread B can’t perceive the change of the shared variable, which is called activity failure.
How do you deal with failure of activity?
The most common actions to have a happens-before relationship between two threads on shared variables are volatile or locking.
Just write down the points of activity failure, not the point here, the point is below.
As the book says, this is acceptable, and this kind of optimization is called ascending.
Speaking of the promotion of the two words, I can not think of what, but see the word, a little interesting.
In a flash, I came up with something about Just In Time (JIT) compilation In Understanding the Java Virtual Machine.
Understanding the Java Virtual Machine in Depth and Effective Java.
Although Effective Java doesn’t describe in detail what this promotion is, we have reason to believe that it refers to the Loop Expression recall described in Understanding the Java Virtual Machine.
The JIT does this for us.
How else can we test this?
At run time, configure the following parameters, which means to disable JIT compiler loading:
-Djava.compiler=NONE
Same code with JIT optimization disabled. The normal operation of the program is over.
Combine the above description with this “loop expression extrapolation”. Now, you should be able to smell it.
And there’s a very, very important piece of information that I can tell you.
A variable stopRequested that is not volatile is useful in both child threads and the main thread. The Java memory model simply does not guarantee that background threads will “see” changes to stopRequested values made by the main thread, rather than never seeing them.
With volatile, the JVM must ensure that stopRequested is visible.
Without volatile, the JVM tries to keep stopRequested visible.
You may be asking, what is the promotion from left to right, can you be a little bit more detailed, a little bit lower down?
B: Sure. You can dive into assembly language. How to operate, you see R big these two links, very hardcore, although you may not understand, but looking at it is to kowtow, do not read more than three times, you may not know what he is saying:
https://hllvm-group.iteye.com/group/topic/34932
https://www.iteye.com/blog/rednaxelafx-644038
Copy the code
Let me just jump to the big conclusion:
So again, back to the point I made at the beginning of this article: different machines, different JVMS, and different cpus may have different effects.
However, since most of our classmates are using HotSpot Server mode, the results are the same.
At the end of this section, we return to the program thrown in the [first question] section of this article:
The while loop here is exactly the same as above. So you know why this program doesn’t end normally?
Not only do you know, but you can answer a little deeper than volatile.
Since flag was not volatile, and flag remained false for a certain number of times during the child thread’s 100ms sleep, the JVM’s just-in-time compilation was triggered. In the case of Loop Expression retention, a dead Loop was formed. However, if volatile is used to modify the flag variable to ensure the visibility of the flag, it will not be promoted.
For example, the following program comments lines 14 and 16, while loop, loop 3359 times (the number of times depends on the machine), read the flag is true, did not trigger instant compilation, so the program ends normally.
Output statements
Next, let’s look at the effect of the output statement on the program:
First, we know that the program will end normally after the output statement is added at line 24.
After our analysis above, we can also derive. The JVM did not JIT after the output statement was added.
Click on the println method and you can see that synchronized is called internally.
I need to discuss this question from three perspectives:
Angle one – Stack Overflow
Found this address on Stack Overflow:
https://stackoverflow.com/questions/25425130/loop-doesnt-see-value-changed-by-other-thread-without-a-print-statement?nor edirect=1&lq=1
It’s the same problem we have here. Here is an answer to this question, which is very good and has received unanimous praise from everyone:
The answer is reasonable from phenomenon to principle to solution. I suggest you read it.
Here I parse only the answers to the output statements relevant to this article:
Let me combine my understanding with this answer to explain:
The synchronous method prevents caching pizzaArrived (our stop) during the loop.
Strictly speaking, to ensure visibility of variables, two threads must synchronize on the same object. If only one thread is operating synchronically on an object, the JVM can ignore it with JIT (escape analysis, lock elimination).
However, the JVM is not smart enough to prove that other threads won’t call println after pizzaArrived is set, so it can only assume that other threads might call println. (So there are synchronous operations)
Therefore, if system.out.println is used, the JVM will not be able to cache variables during the loop.
This is why, when there is a print statement, the loop ends normally, even though this is not a correct operation.
Angle two. – Doug Lea
This Angle is essentially the same as Angle one. But thanks to the blessing of Doug Lea, it should be mentioned separately, big man, that it must be worth it.
In this book by Doug Lea:
There is a section devoted to visibility:
He starts by saying that the writer thread releases the lock, and the reader thread then acquires the same lock.
That’s the conventional wisdom. But then he said In essence.
Essentially, a thread releasing a lock forces all write operations in working memory that occurred before the lock was released to be flushed to main memory.
Acquiring the lock forces a new reload of accessible values into the thread’s working memory.
Angle three – IO operation
The third Angle is less synchronized.
From this perspective, the explanation goes like this: As we’ve seen before, even if a variable is not volatile, the JVM does its best to keep memory visible. However, if the CPU is constantly busy, the JVM cannot force it to refresh memory, so the CPU has no way to keep the memory visible.
After system.out. println is added, the CPU is not so busy (compared to the previous endless loop) due to synchronized. At this point, the CPU may have time to make the memory visible, so the while loop can be terminated.
(Not to mention coarsening, I don’t think this is the right answer.)
From the analysis of the above three angles, we can draw two conclusions
1. Influence of synchronized output statements.
2. The output statement gives the CPU time to refresh memory. For example, in my example, changing the output statement to new File() would also work fine.
But honestly, I don’t know which conclusion is right, so you can judge.
Sleep statement
An example of the effect of a sleep statement on a program looks like this:
Similarly, I found a related question on Stack Overflow:
https://stackoverflow.com/questions/42676751/thread-sleep-makes-compiler-read-value-every-time
Here’s one answer:
With this answer, LET me explain why our test program does not have an infinite loop.
For sleep, we can read the official documentation:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.3
Done in the document’s while loop is also unvolatile.
There are two lines in it that are particularly important (circled in red above) :
1.Thread.sleep has no synchronization semantics (as does thread. yield). The compiler does not have to flush writes cached in registers to shared memory before calling Thread.sleep, nor does it have to reload values cached in registers after calling Thread.sleep.
2. The compiler can ** read the done field only once.
In particular, the second point, notice the free in the document. I mean, it’s like I’m in the middle of something.
Free means that the compiler can choose to read only once, or it can choose to read every time, which is what freedom means. This is the compiler’s choice.
Volatile —
Then we look at the third transformation point:
The change is in line 9, where the variable I is volatile.
If we run with the following JVM parameters:
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintAssembly
-XX:CompileCommand=dontinline,*VolatileExample.main
-XX:CompileCommand=compileonly,*VolatileExample.main
Copy the code
You can see the following output:
At line 23, there is a lock prefix. The LOCK instruction acts as a memory barrier. The “Store” and “write” operations in Java memory mode are triggered.
This is volatile and will not be explained in detail.
Some people may think about happens-before. Unfortunately, that’s not true.
Why is that?
The main thread reads the non-volatile flag and writes the volatile I. However, only non-volatile flags are written to the child thread.
How do you establish the relationship between a child thread’s happens-before write to flag and the main thread’s read to flag?
I personally understand the reason for the normal end of the program in this place: coincidence!
Coincidentally, it’s possible that at some point the variables I and flag are in the same CPU cacheline. Because the lock operation keeps the variable I visible while flushing out flag.
What needs to be noted in particular is that this point is purely personal understanding, and I have not found the corresponding data to support the conclusion. Not authoritative and quotable.
The Integer – metaphysics
Look at the final transformation, the transformation of the final blow:
Again, in line 9, we change the variable I from the base int type to the wrapper type Integer.
The program ended normally on my machine. I really do not know why, the purpose of writing out is in case readers know the reason, please give more advice.
If I had to force an explanation, I wonder if the i++ operation involved unboxing the box, causing the CPU to have time to flush the working memory.
Let me change the program a little bit:
Comment out line 9 and add Integer I =0 at line 21.
Yeah, it’s done, too. It just takes a little time. At I = -2147483648.
And -2147483648 is integer.min_value:
It may be the effect of overflow operations. I don’t know.
Don’t ask. Asking is metaphysics.
Leave a hole here and hope you can fill it up later. Also hope to know why the friend can give me some advice, thank you very much.
One last word (for attention)
Going back to the beginning of this article, the correct way to make the program end as expected is to volatile the flag variable. But volatile would be meaningless, and would be meaningless to explore.
Again: the above operations are for research only, not for real scenarios.
The above question about the impact of output statements and sleep on thread safety has bothered me for a long time. It has been 122 days since I first encountered it. Now I am quite clear about these two problems.
However, I ran into the last problem with Integer as I wrote this article. I really don’t know what happened.
Maybe I can fill in the hole.
Perhaps the end of programming is metaphysics.
If you find something wrong, please leave a message and point it out to me so that I can modify it. (I have this quote in every technical article, and I mean it.)
Thank you for reading, I insist on original, very welcome and thank you for your attention.
I am why Technology, a nice sichuan man who is not a big shot, but likes to share, warm and interesting.
Welcome to pay attention to the public account [WHY Technology], adhere to the output of original. Share technology, taste life, wish you and I progress together.