What is this question about?
Truly understand the synchronized keyword
Knowledge points investigated
Use, principle and optimization of synchronized keyword
How should the examinee answer
In Java, the synchronized keyword is a lightweight synchronization mechanism and the one we use most frequently in our work. Synchronized can be used to modify a method or a block of code. So, do you really know anything about synchronized? Mule or horse, let’s take it out for a walk.
2. As for the use of synchronized, I believe that as long as I have done normal Android or Java development work, I will be familiar with it. What’s the difference between adding synchronized in front of a static method and a non-static method? This kind of problem, light text explanation is not persuasive, direct line code verification.
• static lock
public class SynchronizedTest { private static int number = 0; Public static void main(String[] args) {// Create a thread, create a thread for (int I = 0; i < 5; I ++) {new Thread("Thread-" + I) {@override public void run() {try {//sleep thread.sleep (new) Random().nextInt(5) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } / / call the static method SynchronizedTest. TestStaticSynchronized (); } }.start(); }} public static void testStaticSynchronized() {public static void testStaticSynchronized(); System.out.println(thread.currentThread ().getName() + "-> Current number is" + number); }} // logcat log // Thread-4 -> Current number is 1 // Thread-3 -> Current number is 2 // Thread-1 -> Current number is 3 // Thread-0 -> Current number is 4 // Thread-2 -> The current number is 5Copy the code
The code logic is simple: create five threads to operate on the number variable through a static method that, since it is static, simply uses the class name. According to the output of logcat logs, synchronization is achieved without concurrency exceptions.
Here sorted out the latest BAT interview questions, 2021 ship new version!! Friends in need can click:Here, click on this!!!!! , Note: JJ. Hope those who need friends can find a satisfactory job in the first wave of recruitment this year!
• a non-static lock
public class SynchronizedTest { private static int number = 0; Public static void main(String[] args) {// Create a thread, create a thread for (int I = 0; i < 5; I ++) {new Thread("Thread-" + I) {@override public void run() {try {//sleep thread.sleep (new) Random().nextInt(5) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } SynchronizedTest test = new SynchronizedTest(); / / by objects, calling non-static methods test. TestNonStaticSynchronized (); } }.start(); }} / lock * * * * non-static methods/public synchronized void testNonStaticSynchronized () {number++; System.out.println(Thread.currentThread().getName() + " -> " + number); }} // logcat log // Thread-0 -> Current number is 1 // Thread-4 -> Current number is 1 // Thread-2 -> Current number is 3 // Thread-1 -> Current number is 4 // Thread-3 -> The current number is 4Copy the code
The code here is the same logic as the code above, except that because the method does not use the static modifier, you create an object and call the method to manipulate the number variable. Static thread synchronization is not guaranteed. Static thread synchronization is not guaranteed. Static thread synchronization is not guaranteed.
• Let’s do another test, just to keep you in suspense, and look at the code first.
Public class SynchronizedTest {public static void main(String[] args) {// Create a thread to call a non-static method for (int I = 0; i < 5; i++) { new Thread("Thread-" + i) { @Override public void run() { try { Thread.sleep(new Random().nextInt(5) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } // Call non-static method SynchronizedTest test = new SynchronizedTest(); test.testNonStaticSynchronized(); } }.start(); } // Create 5 threads to call static method for (int I = 0; i < 5; i++) { new Thread("Thread-" + i) { @Override public void run() { try { Thread.sleep(new Random().nextInt(5) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } / / by class name call static methods SynchronizedTest. TestStaticSynchronized (); } }.start(); }} public static void testStaticSynchronized() {public static void testStaticSynchronized() { System.out.println("testStaticSynchronized -> running -> " + System.currentTimeMillis()); } / lock * * * * non-static methods/public synchronized void testNonStaticSynchronized () {System. Out. Println (" testNonStaticSynchronized -> running -> " + System.currentTimeMillis()); }} / / / / testNonStaticSynchronized logcat log - > running - > 1603433921735 / / testNonStaticSynchronized - > running - > 1603433921735 // testStaticSynchronized -> running -> 1603433921735 // testNonStaticSynchronized -> running -> 1603433922740 ----- testStaticSynchronized -> running -> 1603433922740 ----- testStaticSynchronized -> running -> 1603433922740 // testNonStaticSynchronized -> running -> 1603433923735 // testNonStaticSynchronized -> running -> 1603433924740 // testStaticSynchronized -> running -> 1603433925736 // testStaticSynchronized -> running -> 1603433925736Copy the code
The code logic looks like this: create 5 threads each to execute static and non-static methods, and watch the output log, paying special attention to the last timestamp. From the log we can find that from the log line 4 and line 5 found that testNonStaticSynchronized and testStaticSynchronized method can be performed at the same time, it shows that the static locked with the statics of each other’s intervention.
After the above three demo analysis, the basic conclusion can be drawn, here is a summary.
• Class lock: When synchronized modifies a static method, it acquires a class lock that applies to all objects of that class.
• Object lock: When synchronized modifies a nonstatic method, it acquires an object lock that applies to the current object on which the method is invoked.
• Class locks, unlike object locks, do not generate mutual exclusion.
3, of course, on how to use the problem, the above brush brush, the interviewer generally will not ask a lot, after all, can not reflect the interviewer forced case. What we need to discuss is how the bottom of the synchronized keyword helps us achieve synchronization. Yes, this is one of the most frequently asked questions during the interview process. The principle of synchronized, we also divided into two cases to consider.
• First, synchronized modifies code blocks.
Public class SynchronizedTest {public static void main(String[] args) {// Synchronized synchronizes the code block synchronized (SynchronizedTest.class) { System.out.println("this is in synchronized"); }}}Copy the code
Synchronized locks an output statement. Synchronized locks an output statement. Synchronized locks an output statement. Since synchronized is simply a Java keyword, to understand the underlying mechanism, decompile the class file using the Javap command to see what its bytecode looks like.
Look at the result of the decompilation, focusing on the areas highlighted in red. We can clearly see that block synchronization is done using monitorenter, which inserts at the start of the synchronized block, and Monitorexit, which inserts at the end of the method and at the exception. The synchronized code block is entered by the Monitorenter directive and ends at the Monitorexit directive.
What is monitor, the important player here? In simple terms, it can be directly understood as a Lock object, but it is implemented by the virtual machine, and the underlying implementation is the operating system dependent Mutex Lock. Any Java object has a Monitor associated with it, or all Java objects are inherently monitor, which explains why we can lock any object when we use the synchronized keyword.
monitorenter
When monitorenter is executed, the current thread attempts to acquire the lock. If the monitor is not locked, or if the current thread already owns the lock on the object, it increments the lock counter and continues. If the lock fails to be acquired, the current thread blocks and waits until the object lock is released by another thread.
monitorexit
Correspondingly, when monitorexit is executed, the lock counter is also reduced by 1. When the counter is equal to 0, the current thread releases the lock and is no longer the owner of the monitor. At this point, other threads blocked by the Monitor can attempt to acquire ownership of the monitor.
Here’s how synchronized modifiers work, and I’m sure you’ve figured it out. Strike while the iron is hot and continue to look at how synchronized modifiers work.
• Second, synchronized modification methods.
public class SynchronizedTest { public static void main(String[] args) { doSynchronizedTest(); } public static synchronized void doSynchronizedTest(){system.out.println ("this is in synchronized"); }}Copy the code
As usual, decompile javAP directly to see bytecode changes.
As a result of the decompression, this method is not synchronized directly by monitorenter and Monitorexit, but it does have an ACC_SYNCHRONIZED identifier in its method description compared to the other common methods. As you can guess, the virtual machine synchronizes methods based on this identifier, which goes something like this: When a virtual machine invokes a method, the calling instruction will first check whether the ACC_SYNCHRONIZED access flag of the method is set. If so, the execution thread will first acquire monitor, execute the method body after the method is successfully obtained, and release monitor after the method is executed. During method execution, no other thread can retrieve the same Monitor object, which also ensures synchronization. The monitor has class locks and object locks.
Here is a brief summary of the principle of synchronized. The synchronized keyword is implemented in two scenarios. Code block synchronization uses the monitorenter and Monitorexit directives, while method synchronization uses the ACC_SYNCHRONIZED identifier. Both forms are fundamentally based on the JVM entering and exiting the Monitor object lock to synchronize operations.
Carry here, is the small happy, but it is not completely over! From the above principle analysis, we know that synchronized keyword is based on the JVM to enter and exit the monitor object lock to achieve operation synchronization, such a preemptive acquisition of monitor lock, performance must be worrying. Synchronized lock has been optimized since JDK1.6. This is a difficult question. Let’s talk about it in detail.
Prior to JDK1.6, synchronized internal locks were heavyweight locks, which meant that threads that did not acquire CPU usage were blocked. However, in the actual running process of the program, there were many cases in which locking did not need to be so heavy, which would cause more thread context switching and consume more resources. Therefore, after JDK1.6, the synchronized lock is optimized, introducing the concepts of biased lock, lightweight lock and heavyweight lock.
• lock information
Those familiar with the principle of synchronized know that when a thread accesses a synchronized code block, it must acquire a Monitor lock to enter the block and release the monitor lock when it exits or throws an exception. Where does the thread need to acquire synchronized lock information? So before we introduce the concept of various locks, we must first try to solve this puzzle.
In learning about the JVM, we learned that an object is made up of three parts: the object header, the instance data, and the alignment population. The object header also stores the runtime data of the object itself, including hash code, GC generation age, and of course, we will talk about lock-related markers, such as lock status flags, thread held locks, biased thread ID, biased timestamp, and so on.
By default, the object header stores the unlocked state. As the program runs, the data stored in the object header will change with the change of the lock flag bit, as shown in the following figure.
The thread retrieves the lock information by retrieving the associated lock identifier in the object header. With that foundation in mind, we’re now ready to take a look at how synchronized locks can be improved step by step.
• biased locking
Locks are used in concurrent scenarios, however, in most cases, locks do not exist in multithreading competition, and even are acquired by the same thread multiple times, so there is no need to spend too much money on lock acquisition, then biased locking emerged.
Biased locks, as the name implies, favor the first thread that accesses the lock. When a thread accesses a block of synchronized code for the first time and attempts to acquire the lock, a bias lock is directly added to the thread, and the thread ID of the bias lock is stored in the lock record of the object header. In this way, the thread does not need to perform CAS operations to lock and unlock the block again. Just check to see if the object header stores bias locks to the current thread. Obviously, the practice of biased lock is no doubt to eliminate the cost of the same thread lock competition, greatly improve the performance of the program.
Of course, if another thread suddenly preempts the lock during the running process, if the CAS operation succeeds in obtaining the lock, the thread ID in the object header is directly replaced with the new thread ID, which will keep the biased lock state. If it fails, the thread holding the biased lock will be suspended, causing STW. The JVM will automatically remove the biased lock and upgrade it to a lightweight lock.
• Lightweight lock
Lightweight locks are located between biased locks and heavyweight locks. Their main purpose is to reduce the performance cost of traditional heavyweight locks using operating system mutex without multithreading competition. Spin locks are a typical implementation of lightweight locks.
Spinlocks principle is very simple, if the thread holding the lock can lock is released in a very short time resources, and the thread lock wait for competition enters don’t need to do a blocking pending state, they just need to wait a little, spin is for operation, such as thread holding the lock immediately after releasing the lock locks, the consumption of further avoid switch threads like this cause.
Spinlocks, of course, also is not the final solution, such as the lock is very competitive, or thread holding the lock need long time to occupy the lock synchronization block, this time is not suitable for using a spin lock, because the spin lock before acquiring a lock has been a spin inspections CPU, that it is useless for the business. If the cost of a thread’s spin is greater than the cost of a thread’s blocking and suspending operation, then spin-locking does more harm than good, so the number of spins is an important threshold. JDK1.5 defaults to 10 spins. Instead, it is determined by the previous spin time on the same lock and the state of the lock owner, and it is generally considered that the optimal time for a thread context switch is the time.
• Heavyweight lock
After repeated failures of spin, it is generally directly upgraded to heavyweight Lock, which is the highest level of Lock. As mentioned in the principle of Synchronized in the previous article, its underlying implementation is based on monitor object, and the essence of monitor is dependent on the implementation of Mutex Lock in the operating system. This is actually a reference to something we talked about in a previous article, the dangers of switching threads too often, right? Since operating systems need to switch from user mode to kernel mode to implement thread switching, the cost of switching is of course very high.
When the JVM check to heavyweight after lock, lock wants to get the thread to block, inserted into a blocking queue, blocked threads will not consume CPU, but blocking or wake up a single thread, all need the above from the user mode to kernel mode, the cost is higher, than really need to perform the consumption of synchronization code.
We went through the process successively, from no-lock to biased lock, then to lightweight lock, and finally to heavyweight lock. Please refer to the overall picture below for the specific upgrade process. The starting point of all this is to optimize performance. In fact, it also gives us a revelation to the front-line developers. It is not that the function is realized, the coding is over.
The last
Think good little partner remember three support oh, the follow-up will continue to update selected technical articles!