Multithreading 05 locksupport -- JMM memory model

LockSupport is the basic thread blocking primitive used to create locks and other synchronization classes.
park() and unpark() in LockSupport are used to block threads and unblock threads respectively

1. Three methods to make threads wait and wake up:

1. Use the wait() method in Object to make the thread wait, and use the notify() method in Object to wake up the thread
2. Use the await() method of Condition in the JUC package to make the thread wait, and use the signal() method to wake up the thread
3.LockSupport class can block the current thread and wake up the specified blocked thread

1.1 the wait() method in object makes the thread wait:

static Object objectLock =new Object();
    public static void main(String[] args) {

        new Thread(()->{
           synchronized (objectLock){
               System.out.println(Thread.currentThread().getName()+" com in--------");
               try {
                   objectLock.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName()+"  Awakened");
           }
        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+" give an announcement");

            }
                },"t2").start();
    }

Problems caused by:

1.synchronized,wait,notify The three must exist at the same time,
2.wait Can't lag behind notify implement

1.2: the await() method of Condition in the JUC package makes the thread wait:

Lock binding multiple conditions: more than 2 threads are called in sequence, and the specified thread is required to wake up accurately. This case is not written here

 static Lock lock =new ReentrantLock();
    static Condition condition =lock.newCondition();
    public static void main(String[] args) {
        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() +"  come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() +"  Awakened");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

                },"t1").start();

        new Thread(()->{
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() +" give an announcement");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"t2").start();

    }

result:

t1  come in
t2 give an announcement
t1  Awakened

Conclusion:

The thread must first obtain and hold the lock. It must be in the lock block (synchronized or lock). It must wait before waking up before the thread can be awakened

1.3 Park & unpark (thread) of locksuport

LockSupport class uses a concept called permission to block and wake up threads. Each thread has a permission,
permit has only two values, 1 and zero. The default is zero.
Permission can be regarded as a (0,1) Semaphore, but unlike Semaphore, the upper limit of accumulation of permission is 1.

public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() +"  come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() +"  Awakened");
        }, "t1");
        t1.start();

        new Thread(()->{
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() +" give an announcement");
                },"t2").start();
    }

result:

t1  come in
t2 give an announcement
t1  Awakened

Advantages: no lock block requirements + no requirements for wake-up waiting sequence

1.3.1: Park & unpark implements multiple blocking of t1 thread. What to do: n

Then open n new threads. t1 thread plus N LockSupport.park() corresponds to the number of other threads.

2.JMM

Three characteristics: visibility. Atomicity, ordering (Prohibition of instruction rearrangement)

2.1 visibility

When a thread modifies the value of a shared variable, whether other threads can immediately know the change. JMM stipulates that all variables are stored in main memory

The visibility of common shared variables in Java is not guaranteed, because the time when data modification is written into memory is uncertain, and "dirty reading" is likely to occur in multi-threaded concurrency, so each thread has its own working memory. The thread's own working memory stores the copy of the main memory copy of the variable made by the thread, and all the operations of the thread on the variable (reading, assignment, etc.) Must be carried out in the thread's own working memory, and can not directly read and write variables in the main memory. Different threads cannot directly access the variables in each other's working memory. The transfer of variable values between threads needs to be completed through the main memory

If there is no visibility guarantee, the thread is dirty read:

1. There is variable x in main memory, and the initial value is 0
2. To add x to 1, thread A copies x=0 into its own private memory, and then updates the value of X
3. The time for thread A to brush back the updated x value to the main memory is not fixed
4. Just when thread A does not brush back x to the main memory, thread B also reads x from the main memory. At this time, it is 0. Operate the same as thread A, and the last expected x=2 will become x=1

2.2 atomicity

It means that an operation is non interruptible, that is, in a multithreaded environment, the operation cannot be disturbed by other threads

2.3 order

In order to provide performance, compilers and processors usually reorder the sequence of instructions.
Instruction rearrangement can ensure the consistency of serial semantics, but there is no obligation to ensure the consistency of semantics between multiple threads, which can produce "dirty reads". In short,
When more than two lines of irrelevant code are executed, the first one may not be executed first. It may not be executed from top to bottom. The execution order will be optimized.


In the single thread environment, ensure that the final execution result of the program is consistent with the result of the sequential execution of the code.
The processor must consider the data dependency between instructions when reordering
In multi-threaded environment, threads execute alternately. Due to the existence of compiler optimization rearrangement, it is uncertain whether the variables used in the two threads can ensure consistency, and the result can not be predicted

2.3.1 a small example:

public void mySort()
{
int x = 11; // Statement 1
int y = 12; // Statement 2
x = x + 5; // Statement 3
y = x * x; // Statement 4
}
Statement execution order:
1234
2134
1324
Q: can the execution order be 4123? no, data dependency is not considered.

2.4 happens before principle:

In JMM,
If the result of one operation needs to be visible to another operation
Or code reordering, there must be a happens before relationship between the two operations

2.4.1 description of the principle of first occurrence:

If all the ordering in the Java memory model is done only by volatile and synchronized, many operations will become very verbose,
But we didn't notice this when writing Java Concurrent code.

We do not add volatile and synchronized to complete the program from time to time, everywhere and time to time, because there is a "happens before" principle restriction and rule under the JMM principle in the Java language

This principle is very important:
It is a very useful means to judge whether there is data competition and whether the thread is safe. Relying on this principle, we can solve all the problems of whether there may be conflict between two operations in a concurrent environment through several simple rules, without falling into the bitter underlying compilation principle of Java memory model.

2.4.2 general principles of happens before:

2.4.2.1:

If an operation happens before another operation, the execution result of the first operation will be visible to the second operation, and the execution order of the first operation is ahead of the second operation.

2.4.2.2:

The existence of a happens before relationship between the two operations does not mean that they must be executed in the order specified by the happens before principle.
If the execution result after reordering is consistent with the result executed according to the happens before relationship, this reordering is not illegal. (1 + 2 and 2 + 1)

Keywords: Java Multithreading

Added by Cannibal_Monkey on Mon, 20 Sep 2021 13:04:14 +0300