read->load->use->assign->store->write

1. Atomicity, orderliness, visibility

visibility

public class Main { public static int data = 0; public static void main(String[] args) { new Thread(() -> data++).start(); new Thread(() -> { while (data == 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }}Copy the code

No visibility: Thread 2 May not immediately see the data updated by thread 1, but caches unupdated data values in its working memory

Visibility: After thread 1 has updated data, thread 2 must see the updated value

Think of visibility at the hardware level

  1. Each processor has its own register, so when multiple processors each run a thread, one variable may be placed in a register, and each thread will not be able to see the variable value changed in the other processing registers.
  2. Then a thread running by a processor writes variables to the store buffer instead of directly updating main memory, so it’s possible that a thread updates variables only to the buffer, not to main memory.
  3. This time even if a processor thread updates after write the buffer to update synchronization to own tell caching (cache or main memory), and then put the update notice to the other processors, but the other processors may be invalid put the update in the queue, no update his cache, When other processor threads read data from the cache, they still read outdated values.

Visibility is guaranteed at the bottom through the MESI protocol, flush processor cache, and REFRESH processor cache, a whole set of mechanisms

Flush cache: Flushes its updated value to the cache (or main memory) and sends a message to the bus notifying other processors that it has changed the value of a variable.

Refresh Processor cache: When a thread in a processor reads the value of a variable, if it detects that another processor’s thread has updated the value of the variable, it must fetch the latest value from the other processor’s cache (or main memory) and update it to its own cache.

The use of memory barriers, at the underlying hardware level, is essentially flush and refresh

volatile boolean flag = false; flag = true; While (flag){}// Reading volatile variables also passes through a memory barrier, triggering refresh operations at the bottom levelCopy the code

atomic

Int I =0,flag=true, int I =0,flag=true

However, the Java language specification does not guarantee atomicity by default for complex operations such as I ++, in which the value of I is read and then updated

Only one thread can operate on a value ata time (read->load->use->assign->store->write). Data++ must be executed independently.

* (special case) In special cases, simple assignment writes of type long/double in 32-bit virtual machines are not guaranteed atomicity

Long I = 30,double =45.0

If multiple threads concurrently execute long I =30, which is 64-bit, it will cause some threads to change the upper 32 bits of I, and some threads to change the lower 32 bits of I, and send long variables to assign values, which will cause problems in 32-bit virtual machines.

order

Several levels of instruction rearrangement:

1. The execution order in their own source code

2. Sequence of compiled code execution

Java has two types of compilers, a static compiler (JAVAC) and a dynamic compiler (JIT). Javac is responsible for compiling the source code in Java files into bytecode in.class files, which is usually compiled after the program is written. The JIT is responsible for compiling the bytecode in the.class file into machine code supported by the JVM’s operating system, usually while the program is running.

During compilation, the compiler may adjust the order of code execution to improve the order of code execution

JIT dynamic compilation results in a very classic instruction rearrangement

MyObject myobj =new MyObject();
Copy the code

Step 1: Use Myobject as a prototype and allocate a chunk of memory to its object instance

objRef = allocate(MyObject.class)
Copy the code

Step 2: for the allocated memory space of the object instance, execute its constructor to initialize the object instance operation

invokeConstructor(objRef)
Copy the code

Step 3: After the previous two are done, it is time to point the objRef pointer to the memory address

myobj = objRef;
Copy the code

It is possible to change to 1->3->2 after JIT compilation

3. Processor execution sequence

Even if you give the processor an order of execution, it may rearrange the code in a different order

4. Memory resort

It’s possible that your processor has hardware components such as caching and write buffers, invalid queues and so on during the actual execution of instructions, and that your instructions may appear to be in a different order than expected

boolean flag = false; // thread 1 prepare(); // Prepare resources flag = true; // thread 2 while(! flag){ Thread.sleep(1000) } execute(); // Perform operations based on prepared resourcesCopy the code

After the command is reshot

flag = false; flag = true; // thread 1 prepare(); // Prepare resources // thread 2 while(! flag){ Thread.sleep(1000) } execute(); // Perform operations based on prepared resourcesCopy the code

After the reorder, thread 1flag=true will execute first, causing thread 2 to skip while waiting for the next code to execute.

This will cause problems with the code logic.

2. Principle of volatile keyword

Volatile is used to address visibility and order (follow)

For volatile variables, the JVM sends a Lock prefix to the CPU, which immediately writes the value to main memory, and because of the MESI cache consistency protocol, all cpus sniff the bus to see if the data in their local cache has been modified.

If someone else is found to have modified a cache, the CPU will expire its local cache, and the thread executing on that CPU will reload the latest data from main memory when it reads that variable

The Lock prefix directive + MESI cache consistency protocol

3. Order reordering and the happens-before principle

The happens-before principle:

Compilers, directives, they can reorder code, they can reorder code, they have to follow certain rules, and this is called the happens-before rule, and the happens-before rule has some special rules that say that you can’t reorder code by the compiler or by the directive, you have to keep the code in order.

  • Sequence rule: In a thread, operations written in the first place must be limited to those written in the second place, in order of code
  • Lock rule: An UNLOCK operation occurs linearly after the same lock operation
  • Rule for volatile Variables: Code before volatile must not rewrite volatile variables, and code after volatile must not rewrite volatile
  • Transfer rule: If operation A precedes operation B and operation B precedes operation C, it follows that operation A precedes operation B
  • Thread start rule: The Thread object’s start() method precedes every action of the Thread.
  • Thread interrupt rule: A call to the threadinterrupt () method occurs before code in the interrupted thread detects the occurrence of an interrupt event
  • Thread termination rule: All operations in a thread occur before the thread terminates
  • Object finalization rule: An object’s initialization completes before the start of its Finalize () method

4. How does volatile underlie visibility and order based on memory barriers

Subsequent complement