Java Virtual machines--memory models and threads
Cache: The processor wants to interact with memory, such as reading, storing the results of the operation, and the computer's storage device and processor operation speed is very different, so add a layer of read and write speed and the processor close to the cache as a buffer between memory and processor-the data needed to copy the operation to the cache, so that the operation can be quickly When the operation is finished, the cache is synchronized back into memory so that the processor does not have to wait for slow memory to read and write.
Each processor has its own cache, all of which share the same main memory , and when the operations of multiple processors involve the same block of main memory, it will result in inconsistent cache data, which is the primary memory to which processor is being synchronized. This is cache consistency . In order to solve this problem, some cache conformance protocols are required.
Java memory model
The main goal of the Java memory model is to define the access rules for each variable in the program, which is the underlying detail of storing variables in the virtual machine with memory and removing variables from memory. The variables here refer to non-thread-private instance fields, static fields, and elements that make up the array object, excluding local variables and method parameters, because they are thread-private and not shared.
In the Java memory model, all variables are stored in main memory, each thread also has its own working memory (analogous to cache), and all of the thread-to-variable operations must be in working memory and cannot be read directly to the variables in main memory The variables in the other's working memory cannot be accessed directly between different threads, and the transfer of variable values between threads is done through main memory .
If you compare the Java memory model with the Java heap, the stack, the main memory corresponds to the object instance portion of the Java heap, and the working memory corresponds to the portion of the virtual machine stack .
Memory interaction
There is a need for interaction between main memory and working memory, with 8 atomic operations in the Java memory model:
- lock: Acts on the main memory variable and identifies it as thread exclusive.
- unlock: Acts on the main memory variable, frees it from the lock state, and releases it before it can be locked by another thread.
- Read: A variable that acts on the main memory, transferring a variable from main memory to working memory for subsequent load actions to use.
- load: A variable in working memory that puts the value of a read operation from the main memory into a variable copy of the working memory.
- Use: A variable acting on a working memory that passes the value of a variable in the working memory to the execution engine, which is performed when the virtual machine needs a bytecode instruction that uses the value of the variable.
- Assign: A variable that acts on a working memory and assigns a value from the execution engine to a variable in the working memory. This operation is performed when the virtual opportunity is to a bytecode instruction that assigns a value to a variable.
- Store: A variable in working memory that transfers the value of a variable in the working memory to main memory for later write operations.
- Write: A variable that acts on the main memory and puts the store operation from the value of the variable in the working memory into the main memory variable.
If a variable is copied from main memory to working memory, it must be performed read and then the load operation (allowing other operations to be inserted between read and load, as long as this order is guaranteed), and if you want to synchronize the variables from the working memory back to main memory, You need to perform a store operation and then perform a write operation (the store and write allow other operations to be inserted, as long as this order is guaranteed).
volatile keyword
When a variable is defined as volatile, it has two properties:
- Visibility . When a thread modifies the value of this variable, the new value can be immediately observed by other threads. Ordinary variables, ordinary variables between the thread of the transmission must pass through the main memory of the "bridge", that is, thread a modified the value of the variable, to the main memory writeback, thread B after the completion of the write-back and then read from the main, the new variable is only visible to thread B.
- Prohibit command rearrangement optimization.
The volatile directive only guarantees "visibility" and does not guarantee thread safety.
Atomicity, Visibility, ordering
- Atomic Nature . The Java memory model can directly guarantee the operation of atomic variables: read, load, assign, use, store, Write,java basic data type access to read and write is generally atomic. Atomic operations such as lock and unlock are--synchronized blocks of synchronized blocks in Java, and the blocks surrounded by them also have atomicity.
- Visibility . The Java memory model synchronizes the new values back to main memory after the variables have been modified, refreshes the variables from the main memory before the variables are read, and these operations have been the "bridge" of the main memory. This is true for both normal and volatile variables, but volatile guarantees that the new value can be immediately synchronized to the main memory, and immediately refreshes from the main memory before use , which can be said to guarantee the visibility of variables in multi-threaded operations. Synchronized and final keywords can also achieve visibility, the former: Before performing a unlock on a variable, you must first synchronize the secondary variable back into main memory (store, write) The latter: The final decorated field is in the constructor once the initialization is complete and the This reference is not passed out (this reference escapes), the value of the final field can be seen in other threads.
- order . In this thread, all operations are orderly, which is manifested as "line range is expressed as serial semantics"; in one thread, all operations are unordered, showing "instruction rearrangement" and "latency of working memory and main memory". Volatile itself prohibits command rearrangement, and synchronized guarantees that a variable is allowed to lock on only one thread at a time .
The principle of antecedent occurrence
If operation a precedes operation B, it actually means that the effect of operation a (modifying the value of the shared variable, sending a message, invoking a method, etc.) can be observed by Operation B before the operation B has occurred. The Java memory model itself has the following first-order relationships, which are not incompatible with instruction rearrangement.
- Procedural Order Principle : The serialization of semantics within a thread
- Lock rule : A unlock operation first occurs after the lock operation facing the same lock
- volatile rule : The write operation of a volatile variable takes precedence over the operation of this variable
- thread Start Rule : the start () method of a thread precedes any action of the secondary thread
- thread Termination rule : All operations of a thread occur first at the end of a thread
- thread Break Rule : The call to the thread's interrupt () method precedes the code of the interrupted thread
- object Finalization Rule : Initialization of an object is done before its finalize () method
- transitivity : If a precedes b,b before C, a precedes C
Thread
There are three main ways to implement a thread.
Using kernel thread implementations
A kernel thread is a thread that is supported directly by the operating system kernel, which completes thread switching, and the kernel dispatches the thread by manipulating the scheduler and is responsible for mapping the thread's tasks to each processor.
Programs generally do not use kernel threads directly, but rather use a high-level interface of kernel threads-lightweight processes-that is, the absorption and layer of what is usually said, each lightweight process has a kernel thread support, which is a one by one corresponding relationship .
Each lightweight process becomes a separate dispatch unit, and even if a lightweight process is blocked in the system call, it does not affect the entire process to continue working. However, lightweight processes are based on kernel threads and require system calls, and system calls are expensive and need to be switched back and forth between the user and kernel states, so lightweight processes consume kernel resources.
Using the user thread implementation
The user thread in the narrow sense is no kernel help, it is completely built on the line libraries of user space, and the relationship between process and user thread is a one-to-many threading model.
The user thread does not need kernel support, but this is also a disadvantage, all operations need to be handled by the user program itself, so the use of user thread implementation of the program is generally more complex.
Hybrid implementations
Mix lightweight processes and user threads. In this mode, both the user thread and the lightweight process exist, and the lightweight process becomes a bridge between the user thread and the kernel thread. The number of user threads and lightweight processes is variable, which can be called many-to-many models.
Java Thread Scheduling
There are basically two kinds of scheduling methods.
- Collaborative Thread Scheduling : The execution time of a thread is determined by the thread itself, and when it finishes its work, it proactively notifies the system to switch to another thread.
- preemptive thread scheduling : Each thread's execution time is allocated by the system, and the thread's switchover is not determined by the thread itself, and there is no problem that a thread is causing the entire process to block.
the thread scheduling used by Java is preemptive thread scheduling .
Thread scheduling in Java is automated by the system, but by setting the priority of the thread, the higher priority threads are more likely to be selected for execution by the system.
Java Thread State
There are 6 types of thread states.
- new: A thread that has not been started since it was created;
- Run (Runnable): Includes running and ready in the operating system thread state, which may or may be waiting for the CPU to allocate execution time to the state
- wait indefinitely (waiting): This state will not be allocated CPU execution time, waiting to be woken by another thread
- deadline Wait (): This state will not be allocated CPU time, but does not need to be another line Cheng wake up, after a certain period of time there will be automatic wake-up system
- blocking (Blocked): The thread is blocked, the thread in the blocking state waits to acquire an exclusive lock that occurs when the other thread discards the lock, and the wait state is different, waiting for a period of time or an explicit wake of another thread, when the program enters the synchronization area , the thread goes into a blocking state.
- End (Terminated): The shape of the terminated thread, indicating that the thread has finished executing.
by @sunhaiyu
2018.6.20
Java Virtual machines--memory models and threads