The problem

Listing 1

class SingleInstance{
    private static single = null;
    private SingeInstance(){}
    public SingleInstance getSingleInstance() {if(single == null)//0
            synchroznied(SingleInstance.class){
                if(single == null){ single = new SingleInstance(); / / 1}}returnsingle; }}Copy the code

The problem with the code above is that orderliness is not guaranteed at 1, i.e., the code is actually divided into two large steps

  • Initialize SingleInstance
  • Assign this object to the variable single

There is uncertainty before and after this step. As soon as the thread hits 1, it may assign the object to Single first but it hasn’t initialized yet. When thread 2 runs at 0, it finds that this condition is not met and returns single. While single is a non-empty reference, it is not a correct object. This is where double check can go wrong.

volatile

You’ve probably heard that volatile single fixes this problem after JDK1.4, but do you know why it fixes it?

The semantics of volatile preserve order and visibility, but not atomicity

visibility

What is visibility?

In order to

count = 0;
couont++;

Copy the code

As an example of an error, this line of code is executed as follows:

  1. Load the value of count from memory into its own thread stack
  2. Incrementing count in its own thread stack
  3. Put the modified value back into main memory.

In the case of multiple threads

  • Thread 1 performs the first operation
  • Thread 2 then performs the first operation
  • Thread 1 performs the next two operations, at which point count in main memory becomes 1.
  • Thread 2 goes on to do the second operation, which is a copy of its stack that’s 0 and increments by 1, and then does the third operation to write the 1 back into main memory.

See the problem? I added it twice and it’s still 1. Something’s wrong, man!

What role does volatile play in this?

When a thread performs an operation such as ****2 above after volatile count, it does not value in the copy, but in main memory.

And even that doesn’t solve the counting problem. Why?

Atomicity is not guaranteed,i++ operation is non-atomic operation

  • Thread 1 increments the value from memory, and the thread copy count becomes 1.
  • And then thread 2 takes a value from main memory, it gets a value of 0, it increments by 1, it writes to main memory, and count becomes 1 in main memory.
  • Thread 1 performs step 3 to write the count of its copy back to main storage, which is still 1.

summary

The visibility semantics of volatile are intended to ensure that the thread takes the latest value from main memory (step 2 above) rather than its own copy, but atomicity is not guaranteed.

order

In the Java memory model, the compiler and processor are allowed to reorder instructions, but the reordering process does not affect the execution of a single-threaded program, but affects the correctness of multithreaded concurrent execution.

Example: Listing 2

Thread A context = initContext(); //1 flag =true; //2 thread Bwhile(! flag){ sleep(100); } dosomething(context);Copy the code

The code is fine in A single thread, but as shown in Listing 2, the code in thread A might be reordered by running code 2 and then code 1 and that would be A problem.

Using volatile prevents reordering, and the language level of reading and writing data is atomic.

atomic

Atomicity is simply indivisibility, so if it’s an atomic operation, it must either be done or not done at all, it can’t be done partially.

conclusion

Volatile fields guarantee visibility and prohibit reordering, but they do not provide atomicity. The reason is that under the condition of multi-threading, the execution order cannot be guaranteed, and there will be thread switching in the middle.

Going back to Listing 1 remember the original question? What’s wrong with this singleton in Listing 1 we’ve already talked about. How do you solve it? This is easily solved by using the volatile keyword, and by adding volatile to the single variable. The reason is that volatile has the ability to disable reordering. So the object is initialized and then assigned to the variable, and the single detected at zero is returned correctly instead of being an incomplete single.