The Java memory model specifies that all variables are stored in main memory, and that each thread has its own working memory. The working memory in a thread holds a copy of the main memory of variables used by the thread. All thread operations on variables are performed in working memory, and variable values are passed between different threads through main memory
volatile
Visibility: When one thread changes the value of a variable, the new value is immediately known to other threads
- A volatile variable has two properties: it ensures visibility to all threads; Disallow instruction reordering optimization
It is important to note that volatile only guarantees visibility, not atomicity. Operations on volatile variables are also unsafe concurrently in operations that do not comply with the following two rules.
- The result does not depend on the current value of the variable, or can ensure that only a single thread changes the value of the variable
- Variables do not need to participate in invariant constraints with other state quantities
- Volatile reordering acts like a memory barrier, guaranteeing that the operations on the modified variables are in order in the program so that no other instruction reordering can cross the barrier
- The visibility of volatile is that assignments to volatile variables are stored and written to main memory so that the variables in main memory are always up to date. In the loading operation, the operation of reading from main memory is performed first, so that each loading variable is the latest in main memory. So that’s visibility
- Atomicity: Access to basic data types is atomic, as are operations between synchronized blocks
- Visibility: The Java memory model relies on main memory as a transfer medium for visibility by synchronizing the new values back to main memory after a variable is modified and flushing the values from main memory before the variable is read. In addition to volatile, synchronized and final provide visibility order: The Java volatile and synchronized keywords guarantee order between threads
Principle of antecedent
Antecedent principle: it is A partial order relationship between two operations defined in the Java memory model. If operation A occurs before operation B, it means that the influence of operation A can be observed by operation B before operation B occurs
Some natural antecedents under the Java memory model:
- Sequence rule: In a thread, previous operations take place before subsequent operations in the sequence of control flow in the program
- Pipe lock rule: an UNLOCK operation occurs first when a later lock operation is performed on the same lock
- Volatile variable rule: Writes to a volalite variable occur first before reads to that variable occur later
- Thread start rule: The start method of the Thread object occurs first for each action of the Thread
- Thread termination rule: All operations in a thread are preceded by the termination detection of the next thread
- Thread interrupt rule: A call to the threadinterrupt method occurs before code in the interrupted thread detects the occurrence of an interrupt event
- Object finalization rule: The finalization of an object (completion of constructor execution) occurs first at the beginning of its Finalize method
- Transitivity: If operation A precedes operation B and operation B precedes operation C, it follows that operation A precedes operation C
Java thread
- The thread scheduling method used in Java is preemptive scheduling
- Java-defined thread states: new, running, wait indefinitely, wait by deadline, block, and, of course, end
Thread safety in Java
The degree of thread safety in Java language from strong to weak can be divided into immutable, absolute thread safety, relative thread safety, thread compatibility and thread opposition
- Immutable: If the shared data is a basic data type, it can be guaranteed to be immutable simply by using the final keyword modifier when defining it. Immutable types include String, enumerated types, wrapper classes for Long and Double, BigInteger, and BigDecimal
- Most of the Thread-safe classes in the Java API are relatively thread-safe, requiring that individual operations on the object be thread-safe, possibly with additional synchronization on the calling side to ensure that the call is correct. Examples include Vector, HashTable, etc
- Thread-compatible means that objects are not thread-safe per se, but can be safely used in a concurrent environment through the correct use of synchronization on the calling side, such as ArrayList, HashMap, etc
Thread-safe implementation
Mutually exclusive synchronization: When multiple threads concurrently access shared data, ensuring that shared data is only used by one (or several, when using semaphore) thread at a time.
- The most basic means of mutually exclusive synchronization is the synchronized keyword. Synchronized locks an object if it explicitly specifies an object parameter; If not explicitly specified, the lock is either an object instance or a Class object
- In addition, ReentrantLock in the java.util.concurrent package can be used to achieve synchronization. ReentrantLock has some additional features compared with synchronized, mainly: wait can be interrupted, can achieve fair lock, lock can bind multiple conditions, etc
Reentrant code: Code can be interrupted at any point in its execution and switched to another piece of code, with no errors in the original program after control is returned. All reentrant code is thread-safe.
- A method is thread-safe if it returns a predictable result that returns the same result as long as it inputs the same data
Welcome to follow my wechat official number, and learn and grow together with me!