volatile is a relatively important keyword in Java. It is mainly used to modify variables that will be accessed and modified by different threads.
This variable can only guarantee two characteristics, one is to ensure orderliness, and the other is to ensure visibility.
So what is orderliness and what is visibility?
So what is orderliness?
In fact, the order of program execution is based on the order of the code, and instruction reordering is prohibited.
It seems natural, but in fact it is not the case. Instruction reordering is for the JVM to optimize instructions, improve program running efficiency, and increase parallelism as much as possible without affecting the execution results of single-threaded programs.
But in a multi-threaded
environment, the order of some codes changes, which may cause logical errors.
And volatile is well-known because of this feature.
How does volatile ensure orderliness?
Many friends say that what is said on the Internet is that volatile can prohibit the reordering of instructions, which ensures that the code program will be executed strictly in the order of the code. This ensures orderliness. Operations on variables modified by volatile will be executed strictly in the order of the code. That is to say, when the code is executed to a variable modified by volatile, the code in front of it must be executed, and the code behind it must not be executed.
If the interviewer does not continue to dig deeper at this time, then congratulations, this question may have been answered, but if the interviewer continues to dig deeper, why is the command rearrangement prohibited? Is it a command rearrangement?
In the execution from source code to instruction, it is generally divided into three rearrangements, as shown in the figure:
We Next, we have to look at how volatile prohibits instruction reordering.
We directly use the code to verify:
public class ReSortDemo { int a = 0; boolean flag = false; public void mehtod1(){ a = 1; flag = true; } public void method2(){ if(flag){ a = a +1; System.out.println("最后的值: "+a); } } }
If someone sees this code, they will definitely say, what will be the result of this code? ?
Some people say it is 2. Yes, if you only call it in a single thread, the result is 2, but if it is called in multiple threads, the final output result is not necessarily the 2 we imagined. This When doing this, both variables must be set to volatile.
If you know more about the singleton mode, you must have paid attention to this volatile. Why?
Let’s take a look at the following code:
class Singleton { // 不是一个原子性操作 //private static Singleton instance; //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生! private static volatile Singleton instance; // 构造器私有化 private Singleton() { } // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
Are you familiar with the singleton pattern above?
Yes, this is **double check (DCL lazy style) **
Some people will say that because of the existence of instruction reordering, the double-ended retrieval mechanism does not work. It must be thread-safe, right, so the synchronized keyword is used to make it thread-safe.
In fact, visibility is the issue of whether modifications to shared variables are immediately visible to other threads in a multi-threaded environment.
So where does his visibility generally appear? Where is it used?
In fact, this variable is generally used, mostly to ensure its visibility, such as a global variable defined, in which there is a loop to determine the value of this variable, and a thread modifies this parameter. When the time comes, this loop will stop and jump to the next execution.
Let’s take a look at the code implementation without using volatile modification:
public class Test { private static boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); } }
The result is definitely as you can imagine,
The running result It must be:
Thread A starts executing:
Thread B starts executing
The logo has changed
Indeed, that’s it.
#If we use volatile, then the execution result of this code will be different?
Let’s try it:
public class Test { private static volatile boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); }
In this way we can see another execution result, and the output statement in the loop can be executed.
That is to say, in thread B, we modify this modified variable, then finally, in thread A, we can successfully read our data Information.
No, let’s look at some code, variables modified by volatile,
public class Test { // volatile不保证原子性 // 原子性:保证数据一致性、完整性 volatile int number = 0; public void addPlusPlus() { number++; } public static void main(String[] args) { Test volatileAtomDemo = new Test(); for (int j = 0; j < 20; j++) { new Thread(() -> { for (int i = 0; i < 1000; i++) { volatileAtomDemo.addPlusPlus(); } }, String.valueOf(j)).start(); }// 后台默认两个线程:一个是main线程,一个是gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } // 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000 System.out.println(Thread.currentThread().getName() + " final number result = " + volatileAtomDemo.number); } }
If atomicity can be guaranteed, then the final The result should be 20000, but the final result is not guaranteed to be 20000 each time. For example:
##main final number result = 17114Three executions, all with different results.Why does this happen? This has something to do with numbermain final number result = 20000
main final number result = 19317
number is split into 3 instructions
Execute GETFIELD to get the original value number in the main memory
Execute IADD to add 1 operation
Execute PUTFIELD and write the value in the working memory back to the main memory
When multiple threads execute the PUTFIELD instruction concurrently, There will be a problem of writing back to the main memory, so the final result will not be 20000, so volatile cannot guarantee atomicity.
The above is the detailed content of Does the Volatile keyword in Java guarantee thread safety?. For more information, please follow other related articles on the PHP Chinese website!