synchronized deep parsing of pain points in Java stamp

Introduction: role, status, impact of not controlling concurrency

  • Usage: object lock and class lock
  • 7 cases of multithreaded access synchronization method
  • Nature: reentrant, non interruptible
  • Principle: add unlock principle, reentrant principle, visibility principle
  • Defects: low efficiency, not flexible enough, unable to predict whether the lock is successfully obtained
  • How to select Lock or Synchronized
  • How to improve performance and how does the JVM decide which thread gets the lock
  • summary

Later, there will be code demonstration, test environment JDK8 and IDEA

1, Introduction

1. Function

It can ensure that at most one thread executes the code at the same time, so as to ensure the effect of concurrency safety.

2. Status

  • Synchronized is a Java keyword, which is supported natively by Java
  • The most basic means of mutually exclusive synchronization
  • Veteran level of concurrent programming

3. Do not control the impact of concurrency

Test: two threads a + + at the same time. Guess the result

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Do not use synchronized, two threads simultaneously a++  
 *  
 * @author JSON  
 */  
public class SynchronizedTest1 implements Runnable{  
    static SynchronizedTest1 st = new SynchronizedTest1();  
  
    static int a = 0;  
  
    /**  
     * Do not use synchronized, two threads simultaneously a++  
     */  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(st);  
        Thread t2 = new Thread(st);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println(a);  
    }  
  
    @Override  
    public void run(){  
        for(int i=0; i<10000; i++){  
            a++;  
        }  
    }  
}  

It is expected to be 20000, but the results of multiple executions are less than 20000

10108  
11526  
10736  
...  

2, Usage: object lock and class lock

1. Object lock

  • Code block form: manually specify the lock object
  • Method lock form: synchronized decoration method. The lock object defaults to this
package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Object lock instance: code block form  
 *  
 * @author JSON  
 */  
public class SynchronizedTest2 implements Runnable{  
    static SynchronizedTest2 st = new SynchronizedTest2();  
  
    public static void main(String[] args) {  
        Thread t1 = new Thread(st);  
        Thread t2 = new Thread(st);  
        t1.start();  
        t2.start();  
        while(t1.isAlive() || t2.isAlive()){  
  
        }  
        System.out.println("run over");  
  
    }  
  
    @Override  
    public void run(){  
        synchronized (this){  
            System.out.println("Start execution:" + Thread.currentThread().getName());  
            try {  
                //Simulation execution content  
                Thread.sleep(3000);  
            }catch (Exception e){  
                e.printStackTrace();  
            }  
            System.out.println("end of execution:" + Thread.currentThread().getName());  
        }  
    }  
}  
package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Object lock instance: synchronized method  
 * @author JSON  
 */  
public class SynchronizedTest3 implements Runnable{  
    static SynchronizedTest3 st = new SynchronizedTest3();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(st);  
        Thread t2 = new Thread(st);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method();  
    }  
  
    public synchronized void method(){  
        System.out.println("Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("end of execution:" + Thread.currentThread().getName());  
    }  
}  

result:

Start execution:Thread-0  
end of execution:Thread-0  
Start execution:Thread-1  
end of execution:Thread-1  
run over  

2. Class lock

Concept: a Java Class may have multiple objects, but only one Class object

Essence: the so-called Class lock is just the lock of Class object

Usage and effect: class lock can only be owned by one object at the same time

Form 1: synchronized loading on static method

Form 2: synchronized(*.class) code block

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Class lock: synchronized loading on static method  
 *  
 * @author JSON  
 */  
public class SynchronizedTest4 implements Runnable{  
  
    static SynchronizedTest4 st1 = new SynchronizedTest4();  
    static SynchronizedTest4 st2 = new SynchronizedTest4();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(st1);  
        Thread t2 = new Thread(st2);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method();  
    }  
  
    public static synchronized void method(){  
        System.out.println("Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("end of execution:" + Thread.currentThread().getName());  
    }  
}  
package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Class lock: synchronized(*.class) code block  
 *  
 * @author JSON  
 */  
public class SynchronizedTest5 implements Runnable{  
    static SynchronizedTest4 st1 = new SynchronizedTest4();  
    static SynchronizedTest4 st2 = new SynchronizedTest4();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(st1);  
        Thread t2 = new Thread(st2);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method();  
    }  
  
    public void method(){  
        synchronized(SynchronizedTest5.class){  
            System.out.println("Start execution:" + Thread.currentThread().getName());  
            try {  
                //Simulation execution content  
                Thread.sleep(3000);  
            }catch (Exception e){  
                e.printStackTrace();  
            }  
            System.out.println("end of execution:" + Thread.currentThread().getName());  
        }  
    }  
}  

result:

Start execution:Thread-0  
end of execution:Thread-0  
Start execution:Thread-1  
end of execution:Thread-1  
run over  

3, 7 cases of multithreaded access synchronization method

  1. Two threads access the same synchronized method of an object at the same time
  2. Two threads access the same synchronized method of two objects at the same time
  3. Two threads access the synchronized method of the same static of two objects at the same time
  4. Two threads access synchronized and non synchronized methods of the same object at the same time
  5. Two threads access different synchronized methods of the same object
  6. Two threads access the static synchronized method and non static synchronized method of the same object at the same time
  7. Will the lock be released after the method throws an exception

Take a closer look at the output of the following example code and pay attention to the output time interval to predict the conclusion

Scenario 1:

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Two threads access the same synchronized method of an object at the same time  
 *  
 * @author JSON  
 */  
public class SynchronizedScene1 implements Runnable{  
    static SynchronizedScene1 ss = new SynchronizedScene1();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss);  
        Thread t2 = new Thread(ss);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method();  
    }  
  
    public synchronized void method(){  
        System.out.println("Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("end of execution:" + Thread.currentThread().getName());  
    }  
}  

Scenario 2:

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Two threads access the same synchronized method of two objects at the same time  
 *  
 * @author JSON  
 */  
public class SynchronizedScene2 implements Runnable{  
    static SynchronizedScene2 ss1 = new SynchronizedScene2();  
    static SynchronizedScene2 ss2 = new SynchronizedScene2();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss1);  
        Thread t2 = new Thread(ss2);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method();  
    }  
  
    public synchronized void method(){  
        System.out.println("Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("end of execution:" + Thread.currentThread().getName());  
    }  
}  

Scenario 3:

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Two threads access the synchronized method of the same static of two objects at the same time  
 *  
 * @author JSON  
 */  
public class SynchronizedScene3 implements Runnable{  
    static SynchronizedScene3 ss1 = new SynchronizedScene3();  
    static SynchronizedScene3 ss2 = new SynchronizedScene3();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss1);  
        Thread t2 = new Thread(ss2);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method();  
    }  
  
    public synchronized static void method(){  
        System.out.println("Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("end of execution:" + Thread.currentThread().getName());  
    }  
}  

Scenario 4:

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Two threads access synchronized and non synchronized methods of the same object at the same time  
 *  
 * @author JSON  
 */  
public class SynchronizedScene4 implements Runnable{  
    static SynchronizedScene4 ss1 = new SynchronizedScene4();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss1);  
        Thread t2 = new Thread(ss1);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        //Simulate two threads accessing {synchronized and non synchronized methods at the same time  
        if(Thread.currentThread().getName().equals("Thread-0")){  
            method1();  
        }else{  
            method2();  
        }  
    }  
  
    public void method1(){  
        System.out.println("method1 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("method1 end of execution:" + Thread.currentThread().getName());  
    }  
  
    public synchronized void method2(){  
        System.out.println("method2 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("method2 end of execution:" + Thread.currentThread().getName());  
    }  
}  

Scenario 5:

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Two threads access different synchronized methods of the same object  
 *  
 * @author JSON  
 */  
public class SynchronizedScene5 implements Runnable{  
    static SynchronizedScene5 ss1 = new SynchronizedScene5();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss1);  
        Thread t2 = new Thread(ss1);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        //Simulate two threads accessing different synchronized methods at the same time  
        if(Thread.currentThread().getName().equals("Thread-0")){  
            method1();  
        }else{  
            method2();  
        }  
    }  
  
    public synchronized void method1(){  
        System.out.println("method1 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("method1 end of execution:" + Thread.currentThread().getName());  
    }  
  
    public synchronized void method2(){  
        System.out.println("method2 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("method2 end of execution:" + Thread.currentThread().getName());  
    }  
}  

Scenario 6:

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Two threads access the static synchronized method and non static synchronized method of the same object at the same time  
 *  
 * @author JSON  
 */  
public class SynchronizedScene6 implements Runnable{  
    static SynchronizedScene6 ss1 = new SynchronizedScene6();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss1);  
        Thread t2 = new Thread(ss1);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        //Simulate two threads accessing static synchronized method and non static synchronized method at the same time  
        if(Thread.currentThread().getName().equals("Thread-0")){  
            method1();  
        }else{  
            method2();  
        }  
    }  
  
    public static synchronized void method1(){  
        System.out.println("method1 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("method1 end of execution:" + Thread.currentThread().getName());  
    }  
  
    public synchronized void method2(){  
        System.out.println("method2 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        System.out.println("method2 end of execution:" + Thread.currentThread().getName());  
    }  
}  

Scenario 7:

In official account, the top architects reply to the "neat structure" and get the surprise package.

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * Will the lock be released after the method throws an exception  
 *  
 * @author JSON  
 */  
public class SynchronizedScene7 implements Runnable{  
    static SynchronizedScene7 ss1 = new SynchronizedScene7();  
  
    public static void main(String[] args) throws Exception{  
        Thread t1 = new Thread(ss1);  
        Thread t2 = new Thread(ss1);  
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("run over");  
    }  
  
    @Override  
    public void run(){  
        method1();  
    }  
  
    public synchronized void method1(){  
        System.out.println("method1 Start execution:" + Thread.currentThread().getName());  
        try {  
            //Simulation execution content  
            Thread.sleep(3000);  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
        //Simulation anomaly  
        throw new RuntimeException();  
        //System.out.println("method1 end of execution:" + thread. Currentthread() getName());  
    }  
}  

Summary:

1. Two threads access the same synchronized method of an object at the same time

If the same instance has the same lock, other threads must wait and execute in sequence

2. Two threads access the same synchronized method of two objects at the same time

Different instances have different locks, so parallel execution is not affected

3. Two threads access the synchronized method of the same static of two objects at the same time

Static synchronization method is a class lock. All instances have the same lock. Other threads must wait and execute in sequence

4. Two threads access synchronized and non synchronized methods of the same object at the same time

Non synchronized methods are not affected and execute in parallel

5. Two different threads access the same method of synchronize object

The same instance has the same lock, so it is executed sequentially (Note: the lock is this object = = the same lock)

6. Two threads access the static synchronized method and non static synchronized method of the same object at the same time

Static synchronization method is a class lock and non static is an object lock. In principle, it is a different lock, so it is not affected and executed in parallel

7. Will the lock be released after the method throws an exception

The lock will be released automatically. The difference here is lock. Lock needs to display the release lock

Three core ideas:

  • A lock can only be obtained by one thread at the same time, and the thread that does not get the lock must wait (corresponding to scenarios 1 and 5)
  • Each instance has its own lock, and different instances do not affect each other; Exception: lock object is * When class and synchronized are modified by static, all objects share the same lock (corresponding to scenarios 2, 3, 4 and 6)
  • No matter whether the method is executed normally or the method throws an exception, the lock will be released (corresponding to 7 scenarios)

Supplement:

Question: at present, we have entered the method modified by synchronized. In this method, non synchronized methods are called. Is it thread safe? In official account, the top architects reply to the "neat structure" and get the surprise package.

package cn.jsonshare.java.base.synchronizedtest;  
  
/**  
 * At present, we have entered the method modified by synchronized. This method calls the non synchronized method. Is it thread safe?  
 *  
 * @author JSON  
 */  
public class SynchronizedScene8 {  
    public static void main(String[] args) {  
        new Thread(() -> {  
            method1();  
        }).start();  
  
        new Thread(() -> {  
            method1();  
        }).start();  
    }  
  
    public static synchronized void method1() {  
        method2();  
    }  
  
    private static void method2() {  
        System.out.println(Thread.currentThread().getName() + "Enter non Synchronized method");  
        try {  
            Thread.sleep(3000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
        System.out.println(Thread.currentThread().getName() + "End non Synchronized method");  
    }  
}  

Conclusion: This is thread safe

4, Nature

1. Reentrant

It means that after the outer function of the same thread obtains the lock, the inner function can directly obtain the lock again

Typical reentrant locks in Java: synchronized, ReentrantLock

Benefits: avoid deadlock and improve encapsulation

Granularity: threads rather than calls

  • Case 1: prove that the same method is reentrant
  • Case 2: proving reentrant does not require the same method
  • Case 3: it is proved that reentrant is not required to be in the same class

2. Non interruptible

Once the lock is obtained by another thread, if I want to obtain it now, I can only choose to wait or block until another thread releases the lock. If another thread never releases the lock, then I can only wait forever.

In contrast, the Lock class can have the ability to interrupt. First, if I think I've waited too long, I have the right to interrupt the execution of the thread that has obtained the Lock; Second point: if I think I've waited too long and don't want to wait any longer, I can quit.

5, Principle

1. Plus unlock principle (phenomenon, timing, going deep into JVM to see bytecode)

Phenomenon: each instance of a class corresponds to a lock. Each synchronized method must first obtain the lock of the instance of the class calling the method before execution. Otherwise, it will be blocked. After the method execution is completed or an exception is thrown, the lock is released, and the blocked thread can obtain the lock and execute.

Timing of acquiring and releasing lock: built-in lock or monitor lock

package cn.jsonshare.java.base.synchronizedtest;  
  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
  
/**  
 * method1 Equivalent to {method2  
 *  
 * @author JSON  
 * @date 2019-08-29  
 */  
public class SynchronizedToLock1 {  
    Lock lock = new ReentrantLock();  
  
    public synchronized void method1(){  
        System.out.println("implement method1");  
    }  
  
    public void method2(){  
        lock.lock();  
        try {  
            System.out.println("implement method2");  
        }catch (Exception e){  
            e.printStackTrace();  
        }finally {  
            lock.unlock();  
        }  
    }  
  
    public static void main(String[] args) {  
        SynchronizedToLock1 sl = new SynchronizedToLock1();  
  
        //method1 is equivalent to method2  
        sl.method1();  
        sl.method2();  
    }  
}  

Go deep into the JVM to see the bytecode:

...  
monitorenter instructions  
...  
monitorexit instructions  
...  

2. Reentrant principle (lock times counter)

The JVM is responsible for tracking the number of times an object is locked

When a thread locks an object for the first time, the count becomes 1. Every time the same thread obtains a lock on this object again, the count will increase

Every time the task leaves, the count decreases. When the count is 0, the lock is completely released

3. Visibility principle (memory model)

Java Memory Model

The process by which thread A sends data to thread B (JMM control)

synchronized keyword for visibility:

If it is modified by synchronized, any modification made to the object after execution must be written from the thread memory to the main memory before releasing the lock, so the data in the main memory is the latest.

6, Defect

1. Low efficiency

1) . the release of locks is less (thread execution is completed or abnormal release)

2) . you cannot set a timeout when trying to obtain a lock (you can only wait)

3) , cannot interrupt a thread trying to acquire a lock (cannot interrupt)

2. Not flexible enough

The timing of locking and releasing is relatively single, and each lock has only a single condition (an object), which may not be enough

For example, the read-write lock is more flexible

3. Unable to predict whether the lock was successfully obtained

7, Frequently asked questions

1. Synchronize keyword note:

  • Lock object cannot be empty
  • The scope should not be too large
  • Avoid deadlock

2. How to select Lock and synchronized keywords?

Summary suggestions (the principle of giving priority to avoiding mistakes):

  • If you can, give priority to Java util. Various types of concurrent (no need to consider synchronous work and not easy to make mistakes)
  • Give priority to synchronized, which can reduce the amount of code written, thus reducing the error rate
  • If the unique features of Lock or Condition are used, only Lock or Condition can be used

8, Summary

One sentence summary synchronized:

The JVM will automatically lock and unlock by using monitor to ensure that only one thread can execute the specified code at the same time, so as to ensure thread safety. At the same time, it has the characteristics of reentry and non interruption.

Keywords: Android

Added by Creech on Mon, 14 Feb 2022 02:40:03 +0200