Advanced learning journey - multithreaded Synchronized(Synchronized use, lock storage, lock upgrade principle, wait&notify)

Posted by bing_crosby on Wed, 29 Dec 2021 02:30:05 +0100

1. Learning harvest

  • learning method
  • How to ensure thread safety?
  • Basic use of Synchronized
  • Storage of locks
  • Upgrade principle of Synchronized lock
  • Thread communication with wait/notify

2. Learning methods

Scenario - > requirement - > solution - > Application - > principle

  • Scenarios: multithreaded scenarios
  • Requirements: thread safety generated by multithreading in parallel
  • Solution: synchronized
  • Application: several ways of using synchronized, such as object lock, static method lock and code block
  • Principle: bias lock (cas optimistic lock) - > lightweight lock (spin lock) - > heavyweight lock (mutex mutually exclusive)

3. How to ensure thread safety

Thread safety problems will occur in multi-threaded parallel environment. You can manage the access of data state, that is, ensure data security and performance through synchronized lock.

4. Basic use of synchronized

/**
 * @PackageName: com.raven.multithreaded.synchronizedtheory
 * @ClassName: BasicUse
 * @Blame: raven
 * @Date: 2021-08-14 15:46
 * @Description: synchronized Basic use
 * <p>
 * 2 Two forms of expression
 * 2 The difference between two scopes (object lock or class lock) is whether the object is protected across threads
 * The scope of the control lock is determined by the life cycle of the object!!!
 * 1.Modify instance method
 * 2.Modified static method
 * 3.Decorated code block
 */
public class BasicUse {
    private final Object lock;

    public BasicUse(Object lock) {
        this.lock = lock;
    }
    /**
     * Modified static method
     * The lock object is basicuse Class (object lock)
     * It is a class lock, and lock competition will occur when different instances access this method
     * The range of locks is large (the whole method code will be locked)
     */
    public synchronized static void staticMethod() {
        System.out.println("synchronized Modified static method");
    }

    /**
     * Modification fee static method
     * The lock object is a BasicUse object
     * It is an object lock, and there will be no lock competition between different BasicUse instances
     * The range of locks is large (the whole method code will be locked)
     */
    public synchronized void nonStaticMethod() {
        System.out.println("synchronized Modified non static method");
    }

    /**
     * Decorated code block
     * The lock object is a lock object. Lock competition will occur when different BasicUse instances pass in the same lock object
     * The range of the lock is small
     */
    public void codeBlockLock() {
        System.out.println("before sth..");
        synchronized (lock) {
            System.out.println("Lock object is lock ");
        }
        System.out.println("after sth..");
    }

    /**
     * Decorated code block
     * The lock object is a class object of BasicUse, and lock competition will occur
     * The range of the lock is small
     */
    public void codeBlockClass() {
        System.out.println("before sth..");
        synchronized (BasicUse.class) {
            System.out.println("Lock object is BasicUse of class Class object ");
        }
        System.out.println("after sth..");
    }
    /**
     * Decorated code block
     * The lock object is a BasicUse object. There will be no lock competition between different BasicUse instances
     */
    public void codeBlockThis(){
        System.out.println("before sth..");
        synchronized (this) {
            System.out.println("Lock object is BasicUse object ");
        }
        System.out.println("after sth..");
    }

}

/**
 * @PackageName: com.raven.multithreaded.synchronizedtheory
 * @ClassName: BasecUseTest
 * @Blame: raven
 * @Date: 2021-08-14 16:08
 * @Description: Simulate locks using different methods
 */
public class BasicUseTest {

    public static void main(String[] args) {
        // Simulate locks that use synchronized to decorate static methods
//        useStaticMethodLock();
        Object lock = new Object();
        BasicUse basicUse1 = new BasicUse(lock);
        BasicUse basicUse2 = new BasicUse(lock);

        // Simulate locks that use synchronized to decorate non static methods
//        useNonStaticMethodLock(basicUse1, basicUse2);

        // Simulate a class object with synchronized decorated code block - > lock object as BasicUse object
//        useCodeBlockMethodLock(basicUse1, basicUse2);

        // Simulate the use of synchronized modification code block - > lock Object is the incoming Object object, and different threads of different instances are still thread safe
        useCodeLockMethodLock(basicUse1, basicUse2);


    }

    private static void useCodeLockMethodLock(BasicUse basicUse1, BasicUse basicUse2) {
        new Thread(() -> {
            basicUse1.codeBlockLock();
        }).start();
        new Thread(() -> {
            basicUse2.codeBlockLock();
        }).start();
    }

    private static void useCodeBlockMethodLock(BasicUse basicUse1, BasicUse basicUse2) {
        new Thread(() -> {
            basicUse1.codeBlockClass();
        }).start();
        new Thread(() -> {
            basicUse2.codeBlockClass();
        }).start();
    }

    private static void useNonStaticMethodLock(BasicUse basicUse1, BasicUse basicUse2) {
        new Thread(() -> {
            basicUse1.nonStaticMethod();
        }).start();
        new Thread(() -> {
            basicUse2.nonStaticMethod();
        }).start();
    }

    public static void useStaticMethodLock() {
        new Thread(() -> {
            BasicUse.staticMethod();
        }).start();
        new Thread(() -> {
            BasicUse.staticMethod();
        }).start();
    }
}

5. Lock storage and lock upgrade principle?

5.1 realization of lock

jdk1. Before 1.6, the implementation of synchronized was a heavyweight lock, that is, mutex lock. Although it can achieve thread safety, its performance is worrying. After 1.6, the internal implementation was optimized and the concept of lock upgrade appeared
. That is, (biased lock (cas optimistic lock) - > lightweight lock (spin lock) - > heavyweight lock (mutex mutual exclusion))

5.1. 1 deflection lock

cas[(Compare and swap(value,expect,update)] comparison
Achieve atomicity
Optimistic lock

5.1. 2 lightweight lock

What is spin?

boolean cas()

for(;;){// spin
    if(cas){
        return; // Identification succeeded
    }
}

Most threads will release the lock in a very daunt real time after obtaining the lock

Spin will occupy CPU resources, so after the specified spin times, if the lightweight lock has not been obtained, the lock will be upgraded to a heavyweight lock (you can set the spin times through preBlockSpin or use adaptive spin)

5.1. 3 heavyweight lock

When the lock is upgraded to a heavyweight lock, the thread that does not acquire the lock will be blocked (- > blcharged state) and system level thread switching will occur

Each object will have an ObjectMonitor monitor, so any object can become a lock object. You can obtain the monitor object of the current object through the monitor() method
ObjectMonitor is the core to implement the heavyweight lock. monitor is a muteslock (mutual exclusion lock), which can be implemented differently by different os. Therefore, the main reason for the slow heavyweight lock is that there will be system level thread switching (user state < - < - > - > kernel state), and the performance overhead ratio is large

java The method of synchronizing code blocks will pass two more instructions javap -v Class name view
 One is monitor(monitor)enter
 One is monitorexit

.......
    24: monitorexit
    25: goto          33
    28: astore_2
    29: aload_1
    30: monitorexit
......


Implementation of heavyweight lock: thread A executes the monitorenter instruction and competes for the lock object. If monitorenter succeeds, it will obtain the lock object, and other threads will enter the synchronization queue if they can't get the lock object (each blocked thread will join the queue). When thread A executes the monitorexit instruction, it will wake up A thread in the synchronization queue randomly, The thread scrambles for the lock object again.

5.2 storage locked in memory

5.3 lock upgrade principle

synchronized uses different locks in different situations:

Suppose there are two threads, ThreadA/ThreadB

1. When only ThreadA accesses the lock object (in most cases), use the bias lock, mark the ThreadID of ThreadA into the lock object, and modify the lock flag bit to 01.

2. If threada and ThreadB are accessed alternately, upgrade the lock to a lightweight lock, try to obtain the lock object by spinning, change the flag bit to 00, and the lightweight lock points the stack frame to the pointer of the lock record.

3. When multiple threads access at the same time, the lock will be upgraded to a heavyweight lock and will block access.

6.wait and notify

wait,notify,notifyall
Thread communication mechanism

wait: the thread is blocked and added to the synchronization queue to release the current synchronization lock

notify/notifyall: wakes up blocked threads

public class WaitThread extends Thread {

    private Object lock;

    public WaitThread(Object object) {
        this.lock = object;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("wait before:" + Thread.currentThread().getName() + " running");
            try {
                // Can do two things
                // 1. Add the current thread block to the waiting queue
                // 2. Release the lock
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait after:" + Thread.currentThread().getName() + " running");
        }
    }
}


public class NotifyThread extends Thread {

    private Object lock;

    public NotifyThread(Object object) {
        this.lock = object;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("notify before:" + Thread.currentThread().getName() + " running");
            lock.notify();
            System.out.println("notify after:" + Thread.currentThread().getName() + " running");
        }
    }
}

public class WaitAndNotifyTest {

    public static void main(String[] args) {
        Object lock = new Object();
        WaitThread waitThread = new WaitThread(lock);
        waitThread.start();
        NotifyThread notifyThread = new NotifyThread(lock);
        // The notify thread and the wait thread must use the same lock before the notify thread can wake up the wait thread
//        Object lock2 = new Object();
//        NotifyThread notifyThread = new NotifyThread(lock2);
        notifyThread.start();
    }
}

Thread a thread executes the monitorenter instruction, obtains the lock object, and executes some logic. When lock After waiting, he will release the lock and add the current thread to the waiting queue (the waiting queue is not eligible for the lock)

The ThreadB thread executes the monitorenter instruction, obtains the lock object, and executes some logic when executing lock After notify, it will randomly wake up a thread in the waiting queue, move it to the synchronization queue, and then execute the monitorexit command to randomly wake up the thread in the synchronization queue. Goodbye, fight for the lock through the lock competition mechanism and execute the next logic.

Related blog linksgithub
Multithreading foundation of multithreading Application scenario, life cycle, interrupt, reset
Multithreaded SynchronizedSynchronized use, lock storage, lock upgrade principle, wait & notify

Topics: Java Multithreading