Parallelism and concurrency
1. Concurrent refers to multiple threads access the same resources (e.g., seconds kill) 2. Parallel to do multiple things at the same time, popular explanation, refer to https://www.jianshu.com/p/cbf9588b2afbCopy the code
What is volatile
Volatile is a lightweight synchronization mechanism provided by the JVM. Volatile guarantees visibility and orderliness (disallows instruction reordering), but does not guarantee atomicityCopy the code
JMM
(Java Memory Model, or JMM)
JMM is an abstract concept that doesn't really exist. It describes a set of rules or specifications that define how variables (instance variables, class variables, and elements in arrays) in a program can be accessed. > Before locking, the thread must read the latest value of the main memory to its own working memory. > Before unlocking, the thread must refresh the value of the shared variable back to the main memory. > Lock and unlock must be the same lockCopy the code
————————————————————————————————
Concurrency features – Visibility
Because the JVM runs programs in threads, the JVM creates a working memory (also known as stack space) for each thread when it is created. Working memory is an area of memory that is private to each thread.
The JMM specifies that all variables are stored in main memory, which is a shared memory area accessible to all threads.
But the thread must read and write variables in working memory. First copy variables from the main memory to its own working memory, and then operate on variables. After the operation is complete, write variables back to the main memory. You cannot directly operate variables in the main memory. The working memory in each thread stores copies of variables in main memory. By default, what one thread does to its copy of variables is unknown to other threads. This is called memory visibility. The access process of shared variables in JMM is shown as follows:
Volatile ensures visibility validation
/ * * * < h1 > validation volatile visibility < / h1 > * Shared variable num do not use volatile There is no visible * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * Shared variable num Use volatile to ensure visibility. * Num is flushed immediately after being changed and other threads are notified to read it again. */ public class VolatileVisibilityDemo {public static void main(String[] args) { MyData myData = new MyData(); Num = 60 new Thread(() -> {system.out.println (thread.currentThread ().getName() + "\t come in"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } myData.addTo60(); System.out.println(Thread.currentThread().getName() + "\t update num: " + myData.num); }, "AAA").start(); While (mydata.num == 0) {// The main thread waits until num is not equal to 0 System.out.println(thread.currentThread ().getName() + "\t "); } system.out.println (thread.currentThread ().getName()) + "\t "; } } class MyData { volatile int num = 0; void addTo60() { num = 60; }}Copy the code
According to the JMM specification, each thread copies a shared variable from main memory to its working memory, reads and writes the copy, and then writes it back to main memory.
Suppose A thread A has changed the value of the shared variable X, but has not yet written back to main memory. At this time, another thread B writes back to the shared variable X in the main memory. The write back operation of thread B is not visible to thread A. This synchronization delay between the working memory and the main memory causes visibility problems.
————————————————————————————————
Concurrency characteristics – atomicity
Atomicity refers to: indivisible, that is, as a whole, either all or none. In short, other threads either see the result that hasn't been executed, or they see the result that has been executed, and never see the result that is halfway throughCopy the code
Volatile does not guarantee atomic verification
The num++ operation in the following example is actually the following four lines of assembly instruction in the class file
Getfield gets the original value icoonST_1 iadd putfield writes the new value back to main memoryCopy the code
When num++ is performed by multiple threads, some threads may be interrupted from writing putfield back to main memory.
During the time window (nanosecond level) during which these threads were interrupted, other threads may have executed putfield instructions to write back new values to main memory,
But before the interrupted thread can be told to read again,
The CPU wakes up the thread that was interrupted to continue executing putfield instruction, resulting in the write overwrite of num variable in main memory. Finally, all threads finish executing num with incorrect final value (< 20000 in most cases).
/** * <h1> Verify that volatile does not guarantee atomicity </h1> * Atomicity means that it is indivisible, that is, as a whole, that it is either all or none executed, and that it cannot be interrupted. * In short, other threads either see a result that hasn't been executed yet, or they see a result that has been executed, Public class VolatileAtomicityDemo {public static void main(String[] args) {MyData1 MyData1 = new MyData1(); Num = 20000 for (int I = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { myData1.increase(); } }, "thread-" + i).start(); While (thread.activecount () > 2) {thread.yield (); } system.out.println (thread.currentThread ().getName() + "\t end num = "+ mydata1.num); } } class MyData1 { volatile int num = 0; void increase() { num++; }}Copy the code
Volatile does not guarantee atomic problem resolution
AtomicInteger atomic class under JUC package 2 Lock interface under JUC package 3 Synchronized keywordCopy the code
————————————————————————————————
Concurrency – Ordering
When a computer is executing a program, to improve performance, the compiler processor often reorders instructions, which are generally divided into the following three types:
In a single-threaded environment, the result of instruction rebeats is guaranteed to be consistent with the result of sequential code execution.
In order to reorder instructions, the processor must consider data dependencies between instructions ***
However, as multiple threads execute alternately, due to the existence of instruction reordering, it is uncertain whether the variables used in the two threads can maintain consistency, and the results can not be predicted
For example 1:
int x = 11; // statement 1 int y = 12; // statement 2 x = x + 5; Y = x * x; Question 1: What order can be executed without affecting the final result? Question 2: Can statement 4 be reordered to be executed first? No, statement 4 variable y depends on variable 4 and must be assigned to x before statement 4 can be executedCopy the code
Volatile disallows instruction reordering
Volatile prevents instruction reordering through Memory barriers
Memory barriers serve two purposes: to ensure that instructions are executed in an orderly manner, to prohibit reordering, and to ensure memory visibility of certain variables (volatile visibility is implemented using this feature).Copy the code
Because both the compiler and the processor can perform instruction reshoot optimization. Inserting a Memory Barrier between instructions (e.g., statements 4 and 3) tells the compiler and CPU that no instructions can be reordered with that Memory Barrier. That is, *** disallows reordering optimizations before and after memory barriers by inserting barriers.
The memory barrier also serves another purpose: it forces the cache data of various cpus to be updated so that any thread on the CPU can read the latest values of the data
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — – reference source: [www.bilibili.com/video/BV1zb – p9)…