Data race and sequential consistency guarantee
When the program is not synchronized correctly, there will be data race. The java memory model specification defines data competition as follows:
Write a variable in one thread,
Read the same variable in another thread,
And the writes and reads are not ordered by synchronization.
When code contains data races, program execution often produces counterintuitive results (as was the case with the example in the previous chapter). If a multithreaded program is synchronized correctly, the program will be a data race-free program.
JMM makes the following guarantees for the memory consistency of correctly synchronized multi-threaded programs:
If the program is correctly synchronized, the execution of the program will be sequentially consistent – that is, the execution of the program The result is the same as if the program were executed in a sequentially consistent memory model (as we will see shortly, this is a very strong guarantee for the programmer). Synchronization here refers to synchronization in a broad sense, including the correct use of common synchronization primitives (lock, volatile and final).
Sequentially consistent memory model
The sequentially consistent memory model is a theoretical reference model idealized by computer scientists. It provides programmers with strong memory visibility guarantees. The sequential consistency memory model has two major characteristics:
All operations in a thread must be executed in the order of the program.
(Regardless of whether the program is synchronized or not) all threads can only see a single operation execution sequence. In a sequentially consistent memory model, every operation must be performed atomically and immediately visible to all threads.
The sequential consistency memory model provides the programmer with the following view:
Conceptually, the sequential consistency model has a single global memory, this The memory can be connected to any thread through a switch that swings left and right. At the same time, each thread must perform memory read/write operations in the order of the program. From the above figure we can see that at most one thread can be connected to the memory at any point in time. When multiple threads execute concurrently, the switching device in the figure can serialize all memory read/write operations of all threads.
For a better understanding, below we will further explain the characteristics of the sequential consistency model through two schematic diagrams.
Suppose there are two threads A and B executing concurrently. Thread A has three operations, and their order in the program is: A1->A2->A3. Thread B also has three operations, and their order in the program is: B1->B2->B3.
Assume that these two threads use monitors to synchronize correctly: A thread releases the monitor after three operations are performed, and then B thread acquires the same monitor. Then the execution effect of the program in the sequential consistency model will be as shown in the figure below:
Now let's assume that the two threads are not synchronized. The following is the unsynchronized program. Schematic diagram of execution in the sequential consistency model:
Unsynchronized program In the sequential consistency model, although the overall execution order is unordered, all threads can only read to a consistent overall execution sequence. Taking the above figure as an example, the execution order seen by threads A and B is: B1->A1->A2->B2->A3->B3. This guarantee is achieved because every operation in a sequentially consistent memory model must be immediately visible to any thread.
However, there is no such guarantee in JMM. Not only is the overall execution order of unsynchronized programs out of order in JMM, but the order of operation execution seen by all threads may also be inconsistent. For example, before the current thread caches the written data in the local memory and has not refreshed it to the main memory, the write operation is only visible to the current thread; from the perspective of other threads, it will be considered that the write operation has not occurred at all. Executed by the current thread. Only after the current thread flushes the data written in the local memory to the main memory can this write operation be visible to other threads. In this case, the order in which operations are performed will be inconsistent between the current thread and other threads.
Sequential Consistency Effect of Synchronized Programs
Let's use the monitor to synchronize the previous sample program ReorderExample to see how a correctly synchronized program can have sequential consistency.
Please look at the sample code below:
class SynchronizedExample { int a = 0; boolean flag = false; public synchronized void writer() { a = 1; flag = true; } public synchronized void reader() { if (flag) { int i = a; …… } } }
In the above sample code, it is assumed that after thread A executes the writer() method, thread B executes the reader() method. This is a properly synchronized multi-threaded program. According to the JMM specification, the execution results of this program will be the same as the execution results of this program in the sequential consistency model. The following is a comparison chart of the execution timing of the program in the two memory models:
In the sequential consistency model, all operations are executed serially in the order of the program. In JMM, the code in the critical section can be reordered (but JMM does not allow the code in the critical section to "escape" outside the critical section, which will destroy the semantics of the monitor). JMM will do some special processing at the two key time points of exiting the monitor and entering the monitor, so that the thread has the same memory view as the sequential consistency model at these two time points (specific details will be explained later). Although thread A has reordered in the critical section, due to the mutually exclusive execution characteristics of the monitor, thread B here cannot "observe" thread A's reordering in the critical section. This reordering improves execution efficiency without changing the execution results of the program.
From here we can see the basic policy of JMM in specific implementation: without changing the (correctly synchronized) program execution results, try to open up as much convenience as possible for the optimization of the compiler and processor. Door.
Execution characteristics of unsynchronized programs
For multi-threaded programs that are not synchronized or incorrectly synchronized, JMM only provides minimal security: the value read when the thread is executed is either the value of a previous The value written by each thread is either the default value (0, null, false). JMM ensures that the value read by the thread read operation will not appear out of thin air. In order to achieve minimal safety, when the JVM allocates an object on the heap, it will first clear the memory space and then allocate the object on it (the JVM will synchronize these two operations internally). Therefore, when the object is allocated with pre-zeroed memory, the default initialization of the domain is already completed.
JMM does not guarantee that the execution results of an unsynchronized program are consistent with the execution results of the program in the sequential consistency model. Because when an unsynchronized program is executed in the sequential consistency model, it is generally out of order, and its execution results are unpredictable. There is no point in ensuring that an unsynchronized program will have consistent execution results in both models.
Like the sequential consistency model, when an unsynchronized program is executed in JMM, it is generally out of order, and its execution results are unpredictable. At the same time, the execution characteristics of unsynchronized programs in the two models have the following differences:
The sequential consistency model guarantees that operations within a single thread will be executed in the order of the program, while JMM does not guarantee that operations within a single thread will be executed in the order of the program. Execute in the order of the program (such as the above reordering of the correctly synchronized multi-threaded program within the critical section). This has been mentioned before and will not be repeated here.
The sequential consistency model guarantees that all threads can only see a consistent order of operation execution, while JMM does not guarantee that all threads can see a consistent order of operation execution. This has been mentioned before and will not be repeated here.
JMM does not guarantee that read/write operations on 64-bit long and double variables are atomic, while the sequential consistency model guarantees that all memory read/write operations are atomic.
The third difference is closely related to the working mechanism of the processor bus. In a computer, data is passed between the processor and memory via a bus. Each data transfer between the processor and memory is completed through a series of steps, called a bus transaction. Bus transactions include read transactions and write transactions. Read transactions transfer data from memory to the processor, and write transactions transfer data from the processor to memory. Each transaction reads/writes one or more physically contiguous words in memory. The key here is that the bus synchronizes transactions that attempt to use the bus concurrently. While one processor is executing a bus transaction, the bus prohibits all other processors and I/O devices from reading/writing memory. Let us illustrate the working mechanism of the bus through a schematic diagram:
As shown in the figure above, assume that processors A, B and C initiate bus transactions to the bus at the same time. At this time, the bus arbitration will rule on the competition. Here we assume that the bus determines that processor A is competing after arbitration. Win (bus arbitration ensures that all processors have fair access to memory). At this time, processor A continues its bus transaction, while the other two processors have to wait for processor A's bus transaction to complete before they can start to perform memory access again. Assume that while processor A is executing a bus transaction (regardless of whether the bus transaction is a read transaction or a write transaction), processor D initiates a bus transaction to the bus. At this time, processor D's request will be prohibited by the bus.
These working mechanisms of the bus can perform all processors' access to memory in a serialized manner; at any point in time, only one processor can access the memory at most. This feature ensures that memory read/write operations within a single bus transaction are atomic.
On some 32-bit processors, if the read/write operation of 64-bit data is required to be atomic, there will be a relatively large overhead. In order to take care of this kind of processor, the Java language specification encourages but does not require the JVM to have atomicity in reading/writing 64-bit long variables and double variables. When the JVM runs on such a processor, it will split the read/write operation of a 64-bit long/double variable into two 32-bit read/write operations for execution. These two 32-bit read/write operations may be assigned to different bus transactions for execution. At this time, the read/write of this 64-bit variable will not be atomic.
When a single memory operation is not atomic, unexpected consequences may occur. Please look at the diagram below:
#As shown in the figure above, assume that processor A writes a long variable, and processor B wants to read this long variable at the same time. The 64-bit write operation in processor A is split into two 32-bit write operations, and the two 32-bit write operations are assigned to different write transactions for execution. At the same time, the 64-bit read operation in processor B is split into two 32-bit read operations, and the two 32-bit read operations are assigned to the same read transaction for execution. When processors A and B execute according to the timing sequence in the figure above, processor B will see an invalid value that is only "half-written" by processor A.
The above is an in-depth analysis of the Java memory model: sequential consistency. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!