Double-checked Locking: Clever, But Broken

zhaozj2021-02-08  234

DCL: smart, but not working

Do you know the true meaning of synchronization?

summary

Many Java programmers are familiar with Double-Checked Locking methods, which allows you to perform latency initialization, which reduces synchronous costs. Although many books and articles recommend double-checked loacking, unfortunately it does not guarantee certain work. In this article, I will explore some potential perspectives and those temporary discovery, and invested in unknown areas of the Java memory model.

From the principle of very influential Java style to JavaWorld's page, many good Java masters encourage the use of DCL methods, this method has a problem - this seemingly smart method may not work.

What is DCL?

The DCL method is designed to support latency initialization, that is, when a class delay initialization generates object itself until it does need.

Class SomeClass {Private Resource Resource = NULL; Public Resource getResource () {if (resource == null) Resource = new resource (); return resource;}}

Why do you want to delay initialization? Maybe create a resource object is a big overhead, and the SomeClass user is actually not required to call getResource (). In the above example, you can avoid completeness of the resource. Non care, the SomeClass object can be quickly created if it doesn't want to create a resource when constructed. Delay some initialization operation until the user actually needs, this helps the program improve the start speed.

What will happen if you try to use someclass in multi-threaded apps? Then this will be a result of an environmental competition: two threads are executed simultaneously, if resource is null, the result will initialize RESOURCE twice. In multi-threaded environments, you should declare the getResource () into synchronization.

Unfortunately, the synchronous method is very busy, and approximately 100 times more than normal without synchronization. Delayed initialization one of the motivation is efficient, but its appearance is to achieve the rapid start of the program, once the program starts running, you have to accept a long execution time. It is obvious that it is not like a good transaction.

DCL claims to give us the best solution. Using DCL, getResource () method will look like the same: Class SomeClass {private resource = null; public resource getresource () {if (resource == null) {synchronized {if (resource == null) Resource = new Return resource;}}}}

After the first call getResource (), Resource has been instantiated, in most universal code, this can avoid synchronous hits. In the synchronous block, the DCL eliminates the environment's competition by detecting resource again; then ensuring that only one thread will initialize Resource. DCL seems to be like an optimal method - but it can't work.

Contact Java memory model

More precise, DCL cannot guarantee certain work. To understand, we need to take a look at the relationship between JVM and running its computer environment. We need to take a look at the Java Memory Model (JMM), written by Bill Joy, Guy Steler, James Gosling, And Gilad Bracha (Addison-Wesley, 2000) Java language specification has a detailed description, which contains Java operations. Details of threads and memory interactions. Unlike most other languages, Java defines its relationships with potential hardware. Through a formal memory model that runs all Java platforms, you can implement Java "Write once, run everywhere". By comparison, other languages ​​like C and C , there is a lack of a formal memory model; in these languages, the program inherits the memory model running the program hardware platform.

When running in a synchronization (single thread) environment, a program is comparable to the internal memory, or at least its performance is. The program storage entry is located at the memory location, and when the next time the memory location is detected, it is still there.

In fact, the principle is completely different. But through the compiler, Java Virtual Machine (JVM) maintains a complex fantasy, and hardware is concealed. Although we believe that the program will continue to execute in the order illustrated in the program code, but things are not always happening. Compilers, processors, and caches can freely apply our programs and data, as long as they do not affect the calculation results. For example, the compiler can generate an instruction from a distinct program in a different order, and the storage variable is in the register, rather than in memory; the processor can perform instructions in parallel or reverse the order; the cache can change the order of the order Write to the main memory. As long as the environment maintains an AS-IF-Serial syntax, the Java Memory Model (JMM) is subject to all kinds of reordering and optimization. That is, as long as you have completed the same result, you are the same as the result of the instructions in a strict continuous environment.

Compiler, processor, and cache For high performance, you need to rearrange the order in which the program is running. In recent years, we have seen the computing performance of the computer has greatly improved. The improvement of processor clock frequencies have sufficient contribution to high performance, parallel capacity (pipeline form and over-scale architecture execution unit, dynamic instruction allocation table, and flexible execution, precision multi-stage memory cache) increased the main contribution By. Today, the script of the compiler becomes extremely complicated, because in these complexity, the compiler must protect programmers.

When writing a single-threaded program, you won't see the results of these multiple diversity or memory reordering run. However, in a multi-threaded program, the situation is completely different - a thread can read the memory location whose thread has written. If thread A modifies some variables in a certain order, the thread b may not see them in the same order, or you can't see them at all. That may be because the compiler stores the command or temporarily stores the variable in the register, and then writes it to memory; or executes instructions due to the different order of the processor or with the compiler; or due to instructions In different regions of memory, the cache updates the corresponding main memory location with different sequential sequential sequentials. No matter what environment, multi-threaded procedures lack a predictability. Unless you use synchronization to clearly assure the thread has a consistent memory view.

The real meaning of synchronization?

Java processing threads are like each communication and synchronization of their own processors and their local memory and shared main memory. Even in a single-threaded system, the model will become meaningful because the memory cache and the use of the processor's registers. When a thread modifies the location of the local memory, the change needs to eventually appear in the main memory, and the Java Memory Model (JMM) will be developed when the Java Virtual Machine (JVM) must transmit data between local memory and main memory. rule. The structure of Java believes that an over-restrictive memory model will seriously damage the performance of the program. They tried to build a memory model that allows the program to run on modern computer hardware and still guarantee that the thread interacts on a predetermined path. Java uses the synchronized keyword as the main tool to express the interaction between the subscription thread. Many programmers believe that Synchronized strictly performs mutual exclusion (MUTEX) to prevent threads that have more than one thread run in critical parts. Unfortunately, intuition cannot fully describe the meaning of SYNCHRONIZED.

The Synchronized syntax does include a state-based mutual exclusive execution, and also includes rules for interacting between synchronous threads and the main memory. In detail, get and release the lock trigger, memory barrier - forced synchronization between thread local memory and main memory. (Some processors like Alpha, there is an explicit machine instruction to operate the memory barrier.) When a thread exits the SYNCHRONIZED block, it performs a write barrier - it must modify the variables in the block to the main memory before the release lock Go in. Similarly, when entering the Synchronized block, it performs a read barrier - just like local memory has been invalid, it must take out variables related to the main memory from the block.

Normal, use synchronization to ensure that one thread will see another thread in a scheduled manner. However, only the threads A and B remain synchronized on the same object, the Java Memory Model (JMM) can ensure that the thread A changes, and in the Synchronized block, the change in thread A will be fine in the synchronized block. The appearance (or the whole block, or nothing). In addition, the Java Memory Model (JMM) ensures that synchronization in the same object in the SYNCHRONIZED block will be performed as in the order as in the program.

What interrupts will DCL?

DCL believes that there is no problem with the resource field in non-synchronous, but not. That is, assuming the thread A executes a resource = new resource () statement in the Synchronized block; the thread b is entering getResource (). Considering the results of the initialization of memory, in order to create a new resource object, the memory will be assigned; the new object's member field, the constructor will be called; and the SomeClass resource field is assigned to the newly created object.

However, thread b is not executed in the SYNCHRONIZED block, you can see memory than thread a more different sequential operations. These events are seen in the order of thread b (the compiler can also be freely like this rearrangement command): allocate memory, assigning resource, calling the constructor. Assume that the thread b is executed when the memory has been assigned and the resource field has been set, but the constructor has not been called. It feels that resource is not empty, skips the synchronized block, returns a part of the resource object! It is not necessary to say that this result is neither we expect, nor we think about it.

We use this example, many people are very doubts, and the masters attempt to modify the DCL. But their revisions cannot work. We note that the DCL is actually running on different Java virtual machines (JVMs), and a few Java virtual machines (JVM) fully implements the Java Memory Model (JMM). Relying on the details of the implementation, you can't get the correct result of the program, especially mistakes, which requires you to explain the special version of the special Java Virtual Machine (JVM) that DCL involves the write memory and read memory in the non-synchronous block. A thread will have a concurrent danger. Suppose the thread A has completed the initialization of Resource, and exited the SYNCHRONIZED block. At this time, the thread b entered GetResource (). Now Resource has been fully initialized, thread A refreshs to the main memory. The Resource object can be associated with other objects through a member field and save to memory, which will also be refreshed. When thread b sees a newly created Resource because it does not perform a read barrier, it can see the failure value in the Resource Member Field.

Volatile is not what you consider

State the SomeClass's Resource field to Volatile. However, the Java memory model prevents writing variables from being rearranged from the other line, and make sure they are immediately refreshed into memory. It still allows read and write variable variables that have been reordered. That is, unless all resource fields are Volatile. When Resource is set to a newly created resource, thread b can feel the result of the constructor

DCL selection

The most effective way to modify DCL is to avoid it. Avoiding its easiest way is of course synchronization. As long as the variable written by the thread is read by another thread, you should use synchronous to ensure that modifications to other predetermined methods are visible.

Another way to avoid problems is not to initialize, using Eager Initialization, compared to resource until the first use is delayed, Eager Initialization is initialized during constructuring. The class loader synchronizes all class objects, performs a static initialization block when the class is initialized. This means that the results of static initialization during class load automatically are visible to all threads.

A special case for work without synchronization is static. When an instantiated object is a class with a static field without other methods and fields, the Java Virtual Machine (JVM) automatically effects delay initialization. In the following example, RESOURCE will be constructed when the Resource field is first referenced by another class, and the result of memory write resource initialization is automatically visible to all threads.

Class Mysingleton {public static resource resource = new resource ();

This initialization is performed when the Java Virtual Machine (JVM) is initialized. MySingleton does not have other fields and methods. When the resource field is first referenced, class initialization happens.

DCL works at 32-bit simple numbers, if the resource field of SomeClass is Integer type (not long, double), the SomeClass will work as we expect. However, you can't modify DCL issues When you want to initialize an object reference or exceed a simple number.

Will the Java Memory Model (JMM) will be modified?

The University of Maryland is preparing the Java Specification Requirements (JSR) to modify the problem in the current Java Memory Model (JMM). These improvements include:

Java Memory Model (JMM) Purification

Relaxate some requirements, allowing general compilers to most

In the real lack of competitive environments, reduce synchronization performance

This specification has not been submitted to the Java process group in order to prevent rearrangement of variable variables and write other variables in I write this article.

in conclusion

The Java Memory Model (JMM) is the first syntax that attempts to define shared memory in multi-faceted program language specification content. Unfortunately, this is quite complex, difficult to understand, in the user's Java Memory Model (JMM) complex result caused an error and universal misunderstanding of Java Virtual Machine (JVM). If it is difficult to understand synchronization, it is as follows, and it has created a method of unsafe as DCL. This will have a while when we have a simple memory model, you will see the Java specification basically change.

During this time, be careful to test your multi-threaded program to ensure that variable references written by another thread are fully synchronized.

This is my translation, please advise!

2002-9-11 by befresh

转载请注明原文地址:https://www.9cbs.com/read-1261.html

New Post(0)