Pain Point in Java Point - (8) synchronized Deep Resolution
Overview:
Brief introduction: role, status, impact of uncontrolled concurrency
Usage: Object locks and class locks
Seven cases of multithreaded access synchronization methods
Nature: reentrant, uninterruptible
Principle: Adding and Unlocking Principle, Reentry Principle, Visibility Principle
Defects: inefficient, inflexible, unpredictable access to locks
How to choose Lock or Synchronized
How to improve performance, how JVM decides which thread gets the lock
summary
There will be code demonstrations, test environments JDK8, IDEA
I. Brief Introduction
1. Role
It ensures that only one thread can execute the code at most at the same time to ensure the effect of concurrency security.
2. Status
Synchronized is a Java keyword, native Java support
The most basic means of mutually exclusive synchronization
Senior Level of Concurrent Programming
3. The impact of uncontrolled concurrency
Test: Two threads simultaneously a++, guess the result
package cn.jsonshare.java.base.synchronizedtest;
/**
- Without synchronized, two threads simultaneously a++.
* - @author JSON
- @date 2019-08-29
*/
public class SynchronizedTest1 implements Runnable{
static SynchronizedTest1 st = new SynchronizedTest1(); static int a = 0; /** * Without 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 2000, but the results of repeated executions are less than 2000.
10108
11526
10736
...
Usage: Object Locks and Class Locks
1. Object Lock
Code block format: Manually specify lock objects
Method lock form: synchronized modifier method, lock object defaults to this
package cn.jsonshare.java.base.synchronizedtest;
/**
- Object Lock Instance: Code Block Form
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog 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
- @date 2019-08-29
*/
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 { // Analog 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 Locks
== Concept: Java classes may have multiple objects, but only one Class object==
== Essence: The so-called class lock is just the lock of the Class object.==
== Usage and effect: Class locks can only be owned by an object at the same time==
Form 1: synchronized Loading static Method
Form 2: synchronized(*.class) code block
package cn.jsonshare.java.base.synchronizedtest;
/**
- Class lock: synchronized loading static method
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog 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
- @date 2019-08-29
*/
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 { // Analog 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. Seven cases of multi-threaded access synchronization methods
The same synchronized method for two threads accessing an object at the same time
The same synchronized method for two threads accessing two objects at the same time
synchronized method for two threads accessing the same static of two objects at the same time
Synchronized method and non-synchronized method for two threads accessing the same object at the same time
Different synchronized methods for two threads accessing the same object
Synchronized method and non-static synchronized method for two threads accessing the same object at the same time
Will the lock be released after the method throws an exception?
Look carefully at the output of the following example code, and pay attention to the output interval to predict the conclusion.
Scenario 1:
package cn.jsonshare.java.base.synchronizedtest;
/**
- The same synchronized method for two threads accessing an object at the same time
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog 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;
/**
- The same synchronized method for two threads accessing two objects at the same time
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog 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;
/**
- synchronized method for two threads accessing the same static of two objects at the same time
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog 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;
/**
- Synchronized method and non-synchronized method for two threads accessing the same object at the same time
* - @author JSON
- @date 2019-08-29
*/
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(){ // Simulating two threads accessing synchronized and non-synchronized methods simultaneously if(Thread.currentThread().getName().equals("Thread-0")){ method1(); }else{ method2(); } } public void method1(){ System.out.println("method1 Start execution:" + Thread.currentThread().getName()); try { // Analog 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 { // Analog 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;
/**
- Different synchronized methods for two threads accessing the same object
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog 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 { // Analog 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;
/**
- Synchronized method and non-static synchronized method for two threads accessing the same object at the same time
* - @author JSON
- @date 2019-08-29
*/
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(){ // Synchronized method and non-static synchronized method for simulating two threads accessing static 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 { // Analog 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 { // Analog execution content Thread.sleep(3000); }catch (Exception e){ e.printStackTrace(); } System.out.println("method2 end of execution:" + Thread.currentThread().getName()); }
}
Scenario 7:
package cn.jsonshare.java.base.synchronizedtest;
/**
- Will the lock be released after the method throws an exception?
* - @author JSON
- @date 2019-08-29
*/
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 { // Analog execution content Thread.sleep(3000); }catch (Exception e){ e.printStackTrace(); } // Simulated anomaly throw new RuntimeException(); //System.out.println("method1 execution end:" + Thread. current Thread (). getName ()); }
}
Summary:
1. The same synchronized method for two threads accessing an object at the same time
If the same instance has the same lock, other threads must wait and execute sequentially.
2. The same synchronized method for two threads accessing two objects at the same time
Different instances have different locks, so they do not affect parallel execution.
3. synchronized method for two threads accessing the same static of two objects at the same time
Static synchronization method is class lock. All instances are the same lock. Other threads must wait and execute sequentially.
4. Synchronized and non-synchronized methods for two threads accessing the same object at the same time
Non-synchronized methods are unaffected and executed in parallel
5. Different synchronized methods for two threads accessing the same object
The same instance has the same lock, so it is executed sequentially.
6. Synchronized method and non-static synchronized method for two threads accessing the same object at the same time
Static synchronization method is class lock, non-static is object lock, which is different in principle, so it is unaffected and executed in parallel.
7. Will the lock be released after the method throws an exception?
Locks will be released automatically. This distinguishes Lock from the release locks that Lock needs to display.
Three core ideas:
1. A lock can only be acquired by one thread at the same time. Threads that do not have a lock must wait (corresponding to scenarios 1 and 5)
2. Each instance has its own lock, and different instances do not affect each other.
Exceptions: When the lock object is *. class and synchronized are modified by static, all objects share the same lock (corresponding to scenarios 2, 3, 4, 6)
3. Whether the method is normally executed or the method throws an exception, the lock will be released (corresponding to 7 scenarios)
Supplement:
Question: Is it thread-safe to go into a method modified by synchronization, in which a non-synchronized method is called?
package cn.jsonshare.java.base.synchronizedtest;
/**
- At present, we have entered the method modified by synchronization, which calls the non-synchronized method. Is it thread-safe?
* - @author JSON
- @date 2019-08-29
*/
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-government Synchronized Method"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "End of non-compliance Synchronized Method"); }
}
Conclusion: This is thread safe
IV. Nature
1. reentrant
It means that after the outer function of the same thread acquires the lock, the inner function can acquire the lock again directly.
Java's typical re-entrant locks: synchronized, ReentrantLock
Benefits: Avoiding deadlocks and improving 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: Proving reentrant does not require the same class
2. Non-interruptible
Once the lock is acquired by another thread, if I want to get it now, I can only choose to wait or block until the other thread releases the lock. If the other thread never releases the lock, then I can only wait forever.
By 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 a thread that has acquired the lock. Second, if I think I've waited too long to wait any longer, I can exit.
V. Principles
1. The Principle of Adding and Unlocking (Phenomenon, Timing, Deep 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 it can be executed. Otherwise, it will be blocked. The execution of the method completes or throws an exception, the lock is released, and the blocked thread can get the lock and execute it.
Time to acquire and release locks: built-in locks or monitor locks
package cn.jsonshare.java.base.synchronizedtest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
- method1 is 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(); }
}
Deep into JVM to see bytecode:
...
monitorenter instruction
...
monitorexit instruction
...
2. Reentrant Principle (Locking Number Counter)
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. Each time the same thread acquires a lock on the object again, the count increases.
Each time the task leaves, the count decreases, and when the count is zero, the lock is completely released.
3. Visibility Principle (Memory Model)
jmm
avatar
The process of sending data from thread A to thread B (JMM control)
avatar
== synchronized keywords achieve visibility:==
Modified by synchronization, then after execution, any modification to the object must be written from thread memory to main memory before releasing the lock, so the data in main memory is up to date.
VI. Defects
1. Low efficiency
1) less release of locks (thread execution complete or exception release)
2) You cannot set a timeout when trying to get a lock (you can only wait)
3) Can't interrupt a thread trying to get a lock (can't interrupt)
2. Not flexible enough
The timing of locking and releasing is relatively single, and a single condition (an object) for each lock may not be enough.
For example, read-write locks are more flexible
3. Unpredictable success in acquiring locks
Frequent Questions
1. synchronized keyword note:
Lock object cannot be empty
Scope should not be too large
Avoid deadlocks
2. How to choose Lock and synchronized keywords?
Summarize the recommendations (the principle of priority to avoid errors): ___________
If you can, try to give priority to java.util.concurrent classes (no synchronization is required, and it's not easy to make mistakes)
Give priority to synchronized, which reduces the amount of code you write and reduces the error rate.
Use Lock or Condition only if you use the unique features of Lock or Condition
Summary
A sentence summarizing synchronized:
JVM automatically uses monitor to lock and unlock, which ensures that only one thread can execute the specified code at the same time, thus ensuring thread safety, as well as reentrant and uninterruptible characteristics.
Original address https://www.cnblogs.com/JsonShare/p/11433302.html