In-depth understanding of volatile keywords
1.volatile and visibility
We all know that volatile can guarantee visibility, but how is it guaranteed?
This is related to the Happen-before principle. The third provision of this principle is: for a volatile-modified variable, the write operation should be earlier than the read operation of the variable. The specific steps are as follows:
A thread reads the shared variables into the working memory, and at the same time, the B thread also reads the shared variables into the working memory.
After thread A modifies the shared variable, it will be immediately refreshed to the main memory. At this time, the shared variable in the working memory of thread B will be set to be invalid, and the new value needs to be re-read from the main memory. Reflected on the hardware, the Cache line of the CPU is set to an invalid state.
This ensures visibility. Simply put, after a thread modifies the volatile-modified variable and refreshes it to the main memory, it will invalidate the shared variables in the working memory of other threads and needs to be retrieved from the main memory. Read this again.
2. Volatile and orderliness
We all know that volatile can guarantee orderliness, so how is it guaranteed?
volatile ensures orderliness and is relatively straightforward. It prohibits the JVM and processor from reordering instructions for variables modified with the volatile keyword, but the variables before or after the variable can be sorted arbitrarily, as long as the final result is the same as that of the variable. Just keep the results consistent before the change.
Underlying Principle
Variables modified by volatile will be prefixed with a "lock:" at the bottom. The instruction with the "lock" prefix is equivalent to a memory barrier. This is precisely the key to ensuring visibility and orderliness. The main functions of this barrier are as follows:
When instructions are rearranged, the code before the barrier cannot be rearranged behind the barrier, nor can the code after the barrier. Rearrange in front of the barrier.
When executing the memory barrier, ensure that all previous codes have been executed, and the execution results are visible to the code after the barrier.
Force variables in working memory to be flushed to main memory.
The variables in the working memory of other threads will be set to invalid and need to be read from the main memory again.
3. Volatile and atomicity
We all know that volatile cannot guarantee atomicity, so why can't it guarantee atomicity?
Code demonstration:
package com.github.excellent01; import java.util.concurrent.CountDownLatch; /** * @auther plg * @date 2019/5/19 9:37 */ public class TestVolatile implements Runnable { private volatile Integer num = 0; private static CountDownLatch latch = new CountDownLatch(10); @Override public void run() { for(int i = 0; i < 1000; i++){ num++; } latch.countDown(); } public Integer getNum() { return num; } public static void main(String[] args) throws InterruptedException { TestVolatile test = new TestVolatile(); for(int i = 0; i < 10; i++){ new Thread(test).start(); } latch.await(); System.out.println(test.getNum()); } }
Start 10 threads, each thread adds the shared variable num 1000 times, and when all threads have completed execution, print out the final result of num.
There are rarely 10,000. This is because volatile cannot guarantee atomicity.
Cause analysis:
The operation of num consists of three steps:
Read num from main memory into working memory Medium
Add one in the working memory
After the addition is completed, write it back to the main memory.
Although these three steps are all atomic operations, together they are not atomic operations, and each step may be interrupted during execution.
Assume that the value of num is 10 at this time. Thread A reads the variable into its own working memory. At this time, a CPU switch occurs. B also reads num into its own working memory. At this time, the value is also 10. .Thread B modifies the value of num in its own working memory and changes it to 11, but it has not been refreshed to the main memory at this time, so thread A does not know that the value of num has changed. As mentioned before, After the volatile variable is modified, other threads will know it immediately. The prerequisite is that it must be refreshed to the main memory first. At this time, other threads will set the value of the shared variable they are working on to invalid. Because it was not refreshed to the main memory, A stupidly did not know and added one to 10. Therefore, although both threads performed an increment operation in the end, the final result was only added once.
This is why volatile cannot guarantee atomicity.
volatile usage scenarios
According to the characteristics of volatile, ordering and visibility are guaranteed, but atomicity cannot be guaranteed, so volatile can be used for those that do not require atomicity. , or when atomicity has been guaranteed:
Code Demonstration
volatile boolean shutdownRequested public void shutdown() { shutdownRequested = true; } public void work() { while(shutdownRequested) { //do stuff } }
As long as the thread modifies shutdownRequested, the thread executing the work will immediately see it, so it will stop immediately. If not If volatile is added, it will always be true every time it reads data from the working memory and will be executed continuously without knowing that others have stopped it.
Code demonstration:
package com.github.excellent; import java.util.concurrent.ThreadPoolExecutor; /** * 启动线程会被阻塞,flag 从内存读入,会存入寄存器中,下次直接从寄存器取值 * 因此值一直是false * 即使别的线程已经将值更改了,它也不知道 * 加volatile即可。也可以加锁,只要保证内存可见性即可 * @auther plg * @date 2019/5/2 22:40 */ public class Testvolatile { public static boolean flag = false; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(()->{ for(;;) { System.out.println(flag); } }); Thread thread2 = new Thread(()->{ for(;;){ flag = true; } }); thread1.start(); Thread.sleep(1000); thread2.start(); } }
Execution result:
It’s so stupid. Others have modified it without knowing it, and it still outputs false. Just add a volatile and it will be ok.
The above is the detailed content of Deep understanding of the volatile keyword. For more information, please follow other related articles on the PHP Chinese website!