Introduction to the

In this article, we will look at the access scenarios for the seven synchronization methods and see if they are thread-safe. These scenarios are often encountered in multithreaded programming, and are also frequently asked questions in interviews, so both in theory and practice, these are multithreaded scenarios must be mastered.

Eight usage scenarios:

Next, let’s use code to determine whether the following scenarios are thread-safe and why.

  1. A synchronous method in which two threads simultaneously access the same object

  2. A synchronous method in which two threads simultaneously access two objects

  3. A statically synchronized method in which two threads simultaneously access (one or both) objects

  4. Two threads simultaneously access the synchronous and asynchronous methods of (one or both) objects

  5. Two threads access a synchronized method on the same object, which in turn calls an asynchronous method

  6. Different synchronization methods for two threads accessing the same object simultaneously

  7. Both threads access static and non-static synchronized methods simultaneously

  8. A condition in which the JVM automatically releases a lock after a synchronized method throws an exception

Scenario 1: Synchronization method of two threads accessing the same object simultaneously

Analysis: This situation is a classic object lock method lock, two threads compete for the same object lock, so will wait for each other, is thread-safe.

A synchronous method in which two threads simultaneously access the same object is thread-safe. 1Copy the code

We’ve talked about that in the previous article. Synchronized object lock (Java) : Synchronized object lock (Java) : Synchronized object lock (Java)

Scenario 2: Synchronization method of two threads accessing two objects simultaneously

This scenario is a scenario in which the object lock fails because the synchronization method of the two objects is accessed, and the two threads hold the lock of the two threads separately, so they are not restricted to each other. The purpose of locking is to make multiple threads compete for the same lock, but in this case, multiple threads no longer compete for the same lock, but each hold a lock, so our conclusion is:

Two threads accessing synchronous methods of two objects at the same time is not thread-safe. 1Copy the code

Code verification:

Static Condition2 instance1 = new Condition2(); static Condition2 instance1 = new Condition2(); static Condition2 instance2 = new Condition2(); @Override public void run() { method(); } private synchronized void method() {system.out.println (" thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() +", run end "); } public static void main(String[] args) { Thread thread1 = new Thread(instance1); Thread thread2 = new Thread(instance2); thread1.start(); thread2.start(); While (thread1. IsAlive () | | thread2. The isAlive ()) {} System. Out. The println (" end "); }} 123456789101112131415161718192021222324252627282930Copy the code

Running results:

The two threads execute in parallel, so the thread is not safe.

Concern public number: programmer Bai Nannan, receive the end of 2020 summary interview questions

Thread name: thread-0, run start Thread name: Thread-1, run Start Thread: thread-0, run end Thread: Thread-1, run end Test end 12345Copy the code

Code analysis:

“Here’s the thing: “The two threads (thread1, thread2), access to the two objects (instance1, instance2) synchronization method (method ()), two threads has its own lock, cannot form a lock two threads competition situation, so at this time, Synchronized method() is the same as synchronized method(), so method() is a generic method.

To make the lock work, simply apply the method() method as static. This creates a class lock. Multiple instances (instance1, instance2) compete for a class lock, and two threads can execute it serially. And that’s what we’re going to do in the next scene.

Scenario 3: A statically synchronized method in which two threads simultaneously access (one or two) objects

This scenario addresses the thread unsafe problem in Scenario 2, which is implemented with class locks:

A statically synchronized method in which two threads simultaneously access (one or both) objects is thread-safe. 1Copy the code

For the code implementation and detailed explanation of this method, refer to the article “Java synchronized implementation of class lock two ways and principle analysis” in the second part of the “static method lock way to achieve class lock”, will not be retold here.

Scenario 4: Synchronous and asynchronous methods of two threads accessing (one or two) objects simultaneously

In this scenario, where one thread accesses a synchronous method and the other accesses an asynchronous method, does the program execute serially, that is, is it thread-safe? We can be sure that the thread is not safe, and if methods are safe without synchronized, there is no need for synchronized methods. To verify our conclusions:

It is not thread-safe for two threads to access simultaneously synchronized and unsynchronized methods (one or both) of objects. 1 public class Condition4 implements Runnable { static Condition4 instance = new Condition4(); @override public void run() {if (thread.currentThread ().getName().equals(" thread-0 ")) { // thread 0, execute synchronous method0() method0(); } if (thread.currentThread ().getName().equals(" thread-1 ")) {// thread1, method1(); } // private synchronized void method0() {system.out.println (); "+ thread.currentThread ().getName() +"; try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() + ");} system.out.println (" Thread:" + thread.currentThread ().getName() + "); } private void method1() {system.out.println (" thread.currentThread ().getName() + ", "thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() +", common method, end of run "); } public static void main(String[] args) { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); While (thread1. IsAlive () | | thread2. The isAlive ()) {} System. Out. The println (" end "); }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950Copy the code

Running results:

Both threads are executing in parallel, so they are thread-unsafe.

Thread name: thread-0, synchronous method, run start Thread name: Thread-1, common method, run start Thread: Thread-0, synchronous method, run end Thread: Thread-1, common method, run end Test end 12345Copy the code

Results analysis

“Here’s the thing:” Method1 is not modified by synchronized, so it’s not affected by locks. Even in the same object, of course in multiple instances, is not affected by the lock. Conclusion:

Asynchronous methods are not affected by other synchronized methods 1Copy the code

You might think of a similar scenario where multiple threads access a synchronized method on the same object, and that synchronized method calls an unsynchronized method. Would that be thread-safe?

Scenario 5: Two threads access a synchronized method on the same object, and the synchronized method calls an asynchronous method

Let’s experiment with a scenario where two threads call a synchronous method, and a normal method is called within a synchronous method; Use another thread to call a normal method directly and see if it’s thread-safe?

public class Condition8 implements Runnable { static Condition8 instance = new Condition8(); @override public void run() {if (thread.currentThread ().getName().equals(" thread.0 ")) { } else {// call the synchronous method first, and call the normal method method1() within the synchronous method; }} private static synchronized void method1() {system.out.println (); "+ thread.currentThread ().getName() +"; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: + thread.currentThread ().getName() + ");} system.out.println (" Thread: + thread.currentThread ().getName() + "); method2(); } // Private static void method2() {system.out.println (" thread.currentThread ().getName() + ", "thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() +", common method, end of run "); } public static void main(String[] args) {thread0 = new Thread(instance); Thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread0.start(); thread1.start(); thread2.start(); While (thread0. IsAlive () | | thread1. The isAlive () | | thread2. The isAlive ()) {} System. Out. The println (" end "); }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253Copy the code

Running results:

Thread name: thread-1, common method, run start Thread name: Thread-1, synchronous method, run start Thread name: Thread-1, synchronous method, run end, start call common method Thread name: Thread-1, common method, run start Thread Thread-1, common method, end of run Thread name: Thread-2, synchronous method, end of run, start Thread name: Thread-2, synchronous method, end of run, start call common method Thread name: Thread-2, normal, run start Thread: Thread-2, normal, run end Test End 1234567891011Copy the code

Results analysis:

We can see that normal methods are executed in parallel by two threads and are not thread-safe. Why is that?

Because if an asynchronous method is called directly by any other thread, rather than only when the synchronized method is called, multiple threads can execute the asynchronous method in parallel, and the thread is not safe.

To be thread-safe when an asynchronous method is called from a synchronous method, it is necessary to ensure that the entry to the asynchronous method appears only in the synchronous method. However, this control is not elegant, and if an unsynchronized method is called directly by an unknown person, the original thread synchronization is no longer safe. So this is not recommended for projects, but we need to understand the situation, and we need to deal with thread-safety issues in a semantically explicit way that makes it obvious that this is a synchronous approach.

So, the easiest way to do this is to add the synchronized keyword to the synchronized method and make it a synchronized method, which becomes Scene 5: When two threads access different synchronization methods on the same object, it is clear that two synchronization methods on the same object are thread-safe, regardless of which thread calls them.

So the conclusion is:

Two threads accessing a synchronized method on the same object, which in turn calls an asynchronous method, are thread-safe only if no other thread directly calls the asynchronous method. If another thread calls an asynchronous method directly, it is not thread-safe. 1Copy the code

Scenario 6: Two threads accessing the same object simultaneously using different synchronization methods

This scenario also explores the scope of an object lock, which is the scope of all synchronization methods in an object. So, when accessing multiple synchronized methods on the same object, the conclusion is:

When two threads simultaneously access different synchronization methods on the same object, it is thread-safe. 1 public class Condition5 implements Runnable { static Condition5 instance = new Condition5(); @override public void run() {if (thread.currentThread ().getName().equals(" thread.0 ")) { method0(); } if (thread.currentThread ().getName().equals(" thread-1 ")) {// thread1, method1(); }} private synchronized void method0() {system.out.println (); "+ thread.currentThread ().getName() +"; try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() + ");} system.out.println (" Thread:" + thread.currentThread ().getName() + "); } private synchronized void method1() {system.out.println (" thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() + "); Public static void main(String[] args) {Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); While (thread1. IsAlive () | | thread2. The isAlive ()) {} System. Out. The println (" end "); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546Copy the code

Running results:

Thread-safe.

Thread name: thread-1, synchronous method 1, run start Thread: Thread-1, synchronous method 1, run end Thread name: thread-1, synchronize method 0, run start Thread: thread-1, synchronize method 0, run end Test end 12345Copy the code

Results analysis:

Method0 () and method1() synchronized modifier, which does not specify a lock object, but defaults to this as the lock object, so both threads acquire the same lock for the same instance, and the synchronized method executes serially. This is also a reflection of the reentrancy of the synchronized keyword.

Scenario 7: Two threads access both static synchronized and non-static synchronized methods

The essence of this scenario is the question of whether two threads acquire the same lock. Static synchronized methods belong to class locks, and the lock object is (*.class) object. Non-static synchronized methods belong to method locks of object locks, and the lock object is this object. The two threads have different locks and do not interact with each other. Conclusion:

When two threads access static synchronized and non-static synchronized methods at the same time, the thread is not safe. 1Copy the code

Code implementation:

public class Condition6 implements Runnable { static Condition6 instance = new Condition6(); @override public void run() {if (thread.currentThread ().getName().equals(" thread.0 ")) { method0(); } if (thread.currentThread ().getName().equals(" thread-1 ")) {// thread1, method1(); }} // Focus: static synchronized (*.class); Private static synchronized void method0() {system.out.println (); "+ thread.currentThread ().getName() +"; try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() + "); } // Emphasis: synchronized methods belong to the method lock, lock object is (this) object. Private synchronized void method1() {system.out.println (" thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() + "); } public static void main(String[] args) { The lock on thread 1 is a class lock (*.class) object, and the lock on thread 2 is a method lock (this) object. Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); While (thread1. IsAlive () | | thread2. The isAlive ()) {} System. Out. The println (" end "); } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748Copy the code

Running results:

Thread name: thread-1, static synchronization method 0, start Thread name: thread-1, non-static synchronization method 1, start Thread: thread-1, static synchronization method 1, end Thread: thread-1, static synchronization method 0, end test 12345Copy the code

Scenario 8: The JVM automatically releases the lock after a synchronized method throws an exception

This scenario discusses the scenario of synchronized release:

The lock is released only when the synchronized method completes execution or when an exception is thrown. 1Copy the code

So, when an exception occurs in one thread’s synchronization method, the lock is released, and the other thread gets the lock and continues execution. Instead of one thread throwing an exception, another thread is waiting to acquire the lock. This is because the JVM automatically releases the lock object when a synchronized method throws an exception.

Code implementation:

public class Condition7 implements Runnable { private static Condition7 instance = new Condition7(); @override public void run() {if (thread.currentThread ().getName().equals(" thread.0 ")) { method0(); } if (thread.currentThread ().getName().equals(" thread-1 ")) {// thread1, method1(); }} private synchronized void method0() {system.out.println (" thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } // When an exception is thrown, the lock is automatically released by the JVM. You do not need to manually release the lock. "+ thread.currentThread ().getName() +"; throw new RuntimeException(); } private synchronized void method1() {system.out.println (" thread.currentThread ().getName() + "); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" Thread: "+ thread.currentThread ().getName() +", run end "); } public static void main(String[] args) { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); While (thread1. IsAlive () | | thread2. The isAlive ()) {} System. Out. The println (" end "); }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950Copy the code

Running results:

Thread name: thread0, run start Thread name: thread0, throw exception, release lock Thread name: Thread 1, Run to start the Exception in the thread "thread - 0" Java. Lang. RuntimeException ats com.study.synchronize.conditions.Condition7.method0(Condition7.java:34) at Com. Study. The synchronize. The conditions. Condition7. Run (Condition7. Java: 17) at Java. Lang. Thread. Run Thread (Thread. Java: 748) : Thread-1, run end test End 123456789Copy the code

Results analysis:

You can see that threads are executed serially, indicating that they are thread-safe. The JVM automatically releases the lock object of the thread in which the exception occurs, and other threads acquire the lock and continue to execute.

conclusion

This paper summarizes and verifies various use scenarios of synchronized with codes, as well as the causes and conclusions of the occurrence of various scenarios. The theoretical basis of our analysis is that who is the object of synchronized? Are multiple threads competing for the same lock? This condition is used to determine whether a thread is safe. Therefore, with the analysis of these scenarios, we can also determine whether the thread is safe by analyzing the lock object when we use multithreaded programming in the future, so as to avoid such problems.

The 2020 interview questions are divided into 19 modules: Java Basics, Containers, multithreading, reflection, object copy, Java Web, exceptions, Networking, Design patterns, Spring/Spring MVC, Spring Boot/Spring Cloud, Hibernate, MyBatis, RabbitMQ, Kafka, Zookeeper, MySQL, Redis, JVM.

Concern public number: programmer Bai Nannan, access to the above information.