1. Java lock
1.1 Memory semantics of locking
- A lock allows critical sections to be mutually exclusive and allows the thread releasing the lock to send a message to the thread of the same lock
- Locks are released following the happens-before principle (
Lock rule: Unlocking must occur before subsequent locking
) - How locks are represented in Java
Synchronized
和Lock
1.2 Lock Release
After thread A releases the lock, the shared change operation is flushed to main memory
1.3 Obtaining locks
When thread B acquires the lock, the JMM invalidates the thread’s local memory, and the critical section code protected by the monitor must read the shared variable from main memory
1.4 Release and obtain locks
- Lock acquisition has the same memory semantics as volatile reads, as can be seen in my concurrent article @Java Memory Model &Volatile Passage (version 1.7).
- When thread A releases A lock, thread A essentially tells the next thread to acquire the lock that it has changed the shared variable
- When thread B acquires A lock, thread B gets A message from thread A telling it to change the shared variable (before releasing the lock)
- Thread A releases the lock, and thread B then compets for the lock. Essentially, thread A sends A message to thread B via main memory to inform thread B that it has changed the shared variable
2. Review of Synchronized
- Synchronization: Synchronized is an implementation of the Java synchronization mechanism, known as a mutex. The lock it acquires is called a mutex
- Mutex: A lock can only be assigned to one thread at a time and can only be held by one thread at a time
- Role: Synchronized is used to ensure that only one thread can enter the critical area at the same time, and ensure the visibility, atomicity and orderliness of shared variables
- Use: When a thread attempts to access a synchronized code method (block), it must first acquire the lock and release the lock when it exits or throws an exception
3. The use of Synchronized
3.1 Three application methods of Synchronized
Supplement:The advantage of using synchronized blocks is that non-synchronized (this) blocks can still be accessed by other threads
3.2 Rules for using Synchronized
/** * define a test template class. */ public class SynchronizedDemo {public static synchronized void SynchronizedDemo {public static synchronized void SynchronizedDemostaticMethod(){
System.out.println(Thread.currentThread().getName() + "访问了静态同步方法staticMethod");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "End access to static synchronization method");
}
public static void staticMethod2(){
System.out.println(Thread.currentThread().getName() + "Accessed static synchronous method staticMethod2");
synchronized (SynchronizedDemo.class){
System.out.println(Thread.currentThread().getName() + "Synchronizeddemo.class obtained in staticMethod2 method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void synMethod(){
System.out.println(Thread.currentThread().getName() + "访问了同步方法synMethod");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "End access synchronization method synMethod");
}
public synchronized void synMethod2(){
System.out.println(Thread.currentThread().getName() + "Access synchronous method synMethod2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "End access to synchronization method synMethod2");
}
public void method(){
System.out.println(Thread.currentThread().getName() + "Access the common method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "End access to common method method");
}
private Object lock = new Object();
public void chunkMethod(){
System.out.println(Thread.currentThread().getName() + "Called the chunkMethod method");
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "Lock obtained in chunkMethod method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void chunkMethod2(){
System.out.println(Thread.currentThread().getName() + "Accessing the chunkMethod2 method");
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "Lock obtained in chunkMethod2 method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void chunkMethod3(){
System.out.println(Thread.currentThread().getName() + "Accessing the chunkMethod3 method"); Synchronized (this){system.out.println (thread.currentThread ().getName() +"Got this in the chunkMethod3 method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stringMethod(String lock){
synchronized (lock){
while (true){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Copy the code
3.2.1 Common methods are not associated with synchronous method calls
When one thread enters a synchronous method, other threads can normally access other asynchronous methods
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread1 = new Thread(() -> {// call syndemo.method (); }); Thread2 = new Thread(() -> {// call syndemo.synmethod (); }); thread1.start(); thread2.start(); } --------------------- // output: Thread-1 accesses synchronization methods synMethod thread0 accesses common methods method thread0 Ends Accessing common methods method thread-1 ends accessing synchronization methods synMethod // Analysis: The results show that the normal and synchronous methods execute non-blockingCopy the code
3.2.2 All synchronization methods can only be accessed by one thread
When one thread executes a synchronized method, no other thread can access any synchronized method
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { synDemo.synMethod(); synDemo.synMethod2(); }); Thread thread2 = new Thread(() -> { synDemo.synMethod2(); synDemo.synMethod(); }); thread1.start(); thread2.start(); } --------------------- // output: Thread-0 accesses the synchronization method synMethod thread-0 ends the synchronization method synMethod thread-0 accesses the synchronization method synMethod2 Thread-0 ends the synchronization method synMethod2 Thread-1 accesses synMethod2 Thread-1 ends access synMethod2 Thread-1 ends access synMethod Thread-1 ends access synMethod According to the result, the execution of the task is blocked, and Thread-1 must wait for the completion of Thread0 before continuingCopy the code
3.2.3 A block of synchronized code for a lock can only be accessed by one thread at a time
When synchronized blocks are all the same lock, methods can be accessed by all threads, but synchronized blocks with the same lock can only be accessed by one thread at a time
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread1 = new Thread(() -> {// call syndemo.chunkmethod (); synDemo.chunkMethod2(); }); Thread2 = new Thread(() -> {// call syndemo.chunkmethod (); synDemo.synMethod2(); }); thread1.start(); thread2.start(); } --------------------- // Output: Thread0 accesses the chunkMethod method thread1 accesses the chunkMethod method thread0 accesses the chunkMethod lock... Pause and wait... Thread-1 fetches the lock in the chunkMethod method... Pause and wait... Thread0 accesses the chunkMethod2 method and thread0 gets a lock in the chunkMethod2 method... Pause and wait... Thread-1 accesses the chunkMethod2 method. Thread-1 accesses the chunkMethod2 method. Thread-1 accesses the chunkMethod2 method. Comparing lines 18 and 19 shows that even though normal methods have synchronized code blocks, access to the method is non-blocking and any thread is free to enter //2. A comparison of lines 20, 22, and 25 and 27 shows that access to a synchronized block of code for the same lock must be blockedCopy the code
3.2.4 The execution order of multiple synchronization codes accessing the same lock at the same time between threads is variable
- Multiple synchronization codes accessing the same lock at the same time between threads execute in a variable order, even if the same object lock is used, which is quite different from the synchronization method
- ?? Readers can first think about why such a problem?
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread1 = new Thread(() -> {// call syndemo.chunkmethod (); synDemo.chunkMethod2(); }); Thread2 = new Thread(() -> {// call syndemo.chunkmethod2 (); synDemo.chunkMethod(); }); thread1.start(); thread2.start(); } --------------------- // Output: Thread0 accesses the chunkMethod method thread1 accesses the chunkMethod2 method thread0 accesses the lock in the chunkMethod method... Pause and wait... Thread0 accesses the chunkMethod2 method. Thread1 fetches a lock in the chunkMethod2 method... Pause and wait... Thread-1 accesses the chunkMethod method and Thread0 gets the lock in the chunkMethod2 method... Pause and wait... // If the chunkMethod method is used, the chunkMethod lock object is not blocked. If the chunkMethod lock object is not blocked, the chunkMethod lock object is blocked. If the chunkMethod lock object is not blocked, the chunkMethod lock object is blocked. When thread0 accesses the chunkMethod method, the lock will be released first, so thread1 has the chance to acquire the lock and execute it first. If thread0 accesses the chunkMethod method, the lock will be released first. If thread0 accesses the chunkMethod, the lock will be released first. However, it is necessary that access to a block of synchronized code for the same lock must be blocked: all synchronized methods are blocked because the synDemo object has been held internally by the thread and has not been released.Copy the code
3.2.5 Non-blocking access between different locks
- Because the lock objects are different in all three ways, there is no effect on each other
- Except in two cases:
- 1. If the Class object used by the synchronized code block is the same as the Class object, it belongs to the same lock
3.2.3
The principle of - 2. When the synchronized code block uses this, it belongs to the same lock as the synchronized method, as shown above
3.2.2
and3.2.3
The principle of
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> synDemo.chunkMethod() ); Thread thread2 = new Thread(() -> synDemo.chunkMethod3()); Thread thread3 = new Thread(() -> staticMethod()); Thread thread4 = new Thread(() -> staticMethod2()); thread1.start(); thread2.start(); thread3.start(); thread4.start(); } --------------------- // output: Thread-1 accesses the chunkMethod3 method. Thread-1 accesses the staticMethod. Thread-1 accesses the chunkMethod method Thread0 fetches lock in the chunkMethod method and thread3 accesses staticMethod2... Pause and wait... StaticMethod: SynchronizedDemo.class = synchronizedDemo. class Lines 16, 18 and 24, 25 show that access to different blocks of the same lock object is non-blocking. The root cause is lock release and re-competition. When Thread0 accesses chunkMethod, it will release the lock first, and then Thread-1 has a chance to obtain the lock and execute it first. Then, when line 24 and line 25, Thread0 will acquire the lock again and execute it firstCopy the code
3.3 Reentrancy of Synchronized
- Reentrant lock: When a thread requests to hold the critical resource of the object lock again, this is a reentrant lock and the request will succeed
- Implementation: A thread that acquires an object lock is allowed to request the object lock again. For each reentrant, the number of monitor entries is +1
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { synDemo.synMethod(); synDemo.synMethod2(); }); Thread thread2 = new Thread(() -> { synDemo.synMethod2(); synDemo.synMethod(); }); thread1.start(); thread2.start(); } --------------------- // output: Thread-0 accesses the synchronization method synMethod thread-0 ends the synchronization method synMethod thread-0 accesses the synchronization method synMethod2 Thread-0 ends the synchronization method synMethod2 Thread-1 accesses synMethod2 Thread-1 ends access synMethod2 Thread-1 ends access synMethod Thread-1 ends access synMethod In line 16 and line 18, another synchronized method of the current instance object is called in the code block. If the current instance lock is requested again, the method body code will be allowed to execute. This is the most direct representation of the reentrant lockCopy the code
3.4 Synchronized and String Locks
- The same literal is the same lock as the String constant pool cache in the JVM.
- Note: String is strongly not recommended as a lock object and should be used instead as another non-cached object
- Note: If in doubt about literals, review the basics of String first, which are not explained here
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> synDemo.stringMethod("sally"));
Thread thread2 = new Thread(() -> synDemo.stringMethod("sally")); thread1.start(); thread2.start(); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / output: Thread Thread 0-0 Thread Thread - 0 0... Infinite loop... // Analysis: The output is always an infinite loop of Thread-0, meaning that another Thread, thread-1, will not run at all // Reason: Locks in synchronized blocks are the same literalCopy the code
3.5 Synchronized and immutable locks
- Pitfalls: Synchronized also has concurrency problems when using final classes as object locks
- Cause: Due to immutable nature, a new lock object is generated when it is used as a lock but there are still computations inside the synchronized block
- Note: It is strongly not recommended to evaluate a final Class when it is used as a lock object
- Add: String is final Class, but the reason is literal constant pool
public class SynchronizedDemo { static Integer i = 0; //Integer Is final Class public static void main(String[] args) throws InterruptedException {Runnable Runnable = newRunnable() {
@Override
public void run() {
for(int j = 0; j<10000; j++){ synchronized (i){ i++; }}}}; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / output: 14134 / / analysis: with the expected 20000, but there are calculated when using Integer as object lock operation will appear concurrency issuesCopy the code
I = integer.valueof (i.intValue()+1)
ValueOf (); valueOf ();
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
returnnew Integer(i); // Every time there is a new lock!! }Copy the code
3.6 Synchronized and deadlocks
- Deadlock: When threads need to wait for each other to hold a lock, deadlock is formed, resulting in an infinite loop
- Note: No deadlocks in code!!
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "Get lock lock");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(Thread.currentThread().getName() + "Obtain lock2 lock"); }}}); Thread thread2 = new Thread(() -> { synchronized (lock2){ System.out.println(Thread.currentThread().getName() +"Obtain lock2 lock");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "Get lock lock"); }}}); thread1.start(); thread2.start(); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / output: Thread 1 get lock2 Thread - 0 get to lock lock lock... // Analysis: thread 0 acquires the lock, thread 1 acquires the lock2 lock, and thread 1 acquires the lock2 lock"Suspended animation", unable to execute down, resulting in an infinite loop, that is, a deadlock. After that, the system continues to do useless infinite loop, which seriously wastes system resourcesCopy the code
We used JStack to look at the individual threads running on this task and found that both threads were BLOCKED
An obvious observation is that java-level =deadlock. Two threads wait for each other’s lock
Synchronized (version 1.8)
by
Daniel huang, kira
Create, adopt
Creative Commons Attribution – International License for Non-Commercial Use 4.0Grant permission.