Many friends may have heard of the keyword volatile and may have used it. Prior to Java 5, it was a controversial keyword, as using it in programs often resulted in unexpected results. Only after Java 5 did the volatile keyword regain its vitality. Although the volatile keyword is literally simple to understand, it is not easy to use it well.
1. Preface
JMM provides volatile variable definition, final, synchronized blocks to ensure visibility.
For variables modified with volatile, the thread will read the most modified value of the variable every time it uses the variable. Volatile is easily misused and used for atomic operations. I have written a few test examples, you can give it a try.
2. Main program
public class Main{public static void main(String[] args) throws InterruptedException{List<Thread> threadList = new ArrayList<Thread>();for(int i=0; i<10; ++i){Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Single.Holder.instance.add();}});threadList.add(thread);thread.start();}for(Thread thread: threadList)thread.join();System.out.println(Single.Holder.instance.x);}}3. Singleton mode test
1. No volatile, no synchronized
class Single{public int x = 0;public void add(){try {TimeUnit.MILLISECONDS.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}++this.x;}public static class Holder{public static Single instance = new Single();}}Output results: 8, 9, and 10 have all appeared. You can run more and try more and you will find different results.
2. There is volatile, but no synchronized
class Single{public volatile int x = 0;public void add(){try {TimeUnit.MILLISECONDS.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}++this.x;}public static class Holder{public static Single instance = new Single();}}Output result: The maximum number of occurrences is 9 and 10.
3. No volatile, synchronized
class Single{public int x = 0;public synchronized void add(){try {TimeUnit.MILLISECONDS.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}++this.x;}public static class Holder{public static Single instance = new Single();}}Output result: No matter how many times you run, it will be 10.
4. About the application of volatile in DCL (double check lock)
public class LazySingleton {private int someField;private static LazySingleton instance;private LazySingleton() {this.someField = new Random().nextInt(200)+1; // (1)}public static LazySingleton getInstance() {if (instance == null) { // (2)synchronized(LazySingleton.class) { // (3)if (instance == null) { // (4)instance = new LazySingleton(); // (5)}}} return instance; // (6)}public int getSomeField() {return this.someField; // (7)}}First of all, let me explain why this writing method doesn’t work in Java!
Suppose that thread I is calling the getInstance() method for the first time, and then thread II also calls the getInstance() method and getSomeField() method. What we want to explain is that the statement of thread I (1) is not happen-before statement of thread II (7). When thread II executes statement (2) of getInstance() method, since access to instance is not in the synchronous block, thread II may or may not observe thread I's writing to instance in statement (5), that is, the value of instance may be empty or non-empty. We first assume that the value of instance is not empty, so we observe that thread I writes the instance. At this time, thread II will execute statement (6) and directly return the value of this instance, and then call the getSomeField() method on this instance. This method is also called without any synchronization. Therefore, the entire operation of thread II is called without synchronization. This shows that there is no happen-before relationship between the statement (1) of thread I and the statement (7) of thread II. This means that thread II may not be able to observe the value written by thread I to someFiled at statement (1). This is the problem with DCL. It's ridiculous, right? DCL was originally intended to escape synchronization, and it achieved this goal. It is precisely because of this that it was eventually punished. There are serious bugs in such programs, although the probability of such bug being discovered is definitely much lower than the probability of winning the lottery, and it is fleeting. What's more terrifying is that even if it happens, you won't think that it was caused by DCL.
My understanding is that both thread I and thread II have their own working storage. After thread I creates the instance, the time to refresh to memory is uncertain, so it is entirely possible that thread II cannot observe the value written by thread I to someFiled at statement (1).
So because there is an additional happen-before rule added in java 5:
•Write operation to the volatile field happen-before subsequent read operation to the same field.
Using this rule we can declare instance as volatile, that is: private volatile static LazySingleton instance;
According to this rule, we can obtain the statement of thread I (5) -> sentence of thread II (2) (that is, thread), according to the single-thread rule, the statement of thread I (1) -> sentence of thread I (5) and sentence of thread II (2) -> sentence of thread II (7), and according to the delivery rules, there are the statement of thread I (1) -> sentence of thread II (7), which means that thread II can observe the write value of thread I to someFiled in statement (1), and the program can obtain the correct behavior.
Supplement: Before Java5, there is no difference between the synchronous semantics of the final field and other variables. In Java5, once the final variable is set in the constructor (provided that this reference is not leaked in the constructor), other threads will definitely see the values set in the constructor. The problem with DCL is just that we see the default value of the member variable of the object, so we can set the someField variable of LazySingleton to final, so that it can run correctly in java5.
The above content is the knowledge of Volatile keywords in Java introduced to you by the editor. I hope it will be helpful to everyone!