Introduction to the

Multithreading has always been the focus and difficulty in the interview, no matter what level you are at now, you can’t avoid learning the synchronized keyword, this is my experience. Let’s use the interview thinking to make a systematic description of synchronized. If an interviewer asks you, say what you understand about synchronized? You can answer questions about the usage level of synchronized, the JVM level of synchronized, and the optimization level of synchronized. You may be impressed with your interviewer.

Synchronized usage level

Well, you know that synchronized is a lock. For example, you can lock as the the only key lock on the bathroom door, everyone to enter can only take this key to open the toilet door, the key can only be a person in one time, have the key to again and again in the toilet, in a program we call this duplication in and out of the toilet behavior called lock reentrant. It can modify static methods, instance methods, and code blocks, so let’s look at what synchronized means when it comes to code locking.

  • For normal synchronization methods, the object instance is locked.
  • For statically synchronized methods, the Class object of the Class is locked.
  • For synchronized code blocks, objects in parentheses are locked.

First, the concepts of synchronization and asynchrony.

  • Synchronization: Alternate execution.
  • Asynchronous: Simultaneous execution.

For example, eating and watching TV are two things, and then watching TV after eating. In terms of time dimension, these two things are in sequence, which is called synchronization. You can eat while watching TV at the same time, in the dimension of time is no order at the same time, after eating dinner and watching TV, you can go to study, this is asynchronous, the advantage of asynchronous can improve efficiency, so you can save time to learn.

Let’s take a look at the code, which has a very detailed comment that can be copied locally for testing. If you have synchronized based shoes, skip the lock usage section.

/** * @author: jiaolian * @date: Created in 2020-12-17 14:48 * @description: Test static method synchronization and normal method synchronization are different locks, including the use of static code blocks modified by synchronized; * @ modified By: Public class SyncTest {public static void main(String[] args) {public static void main(String[] args) {Service Service = new Service(); /** * Start the following 4 threads to test the m1-M4 methods respectively. */ Thread threadA = new Thread(() -> Service.m1()); Thread threadB = new Thread(() -> Service.m2()); Thread threadC = new Thread(() -> service.m3()); Thread threadD = new Thread(() -> service.m4()); threadA.start(); threadB.start(); threadC.start(); threadD.start(); } /** * This example demonstrates that synchronized static methods and ordinary methods do not acquire the same lock, because they are asynchronous, which is equivalent to synchronous execution; */ private static class Service {/** * m1 method synchronized Service.class */ public synchronized static void m1() {system.out.println ("m1 getLock "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m1 releaselock"); The m1 and m2 methods are synchronized when a thread AB is started at the same time. We can prove that M1 and M2 are the same lock. */ public synchronized static void m2() { System.out.println("m2 getlock"); System.out.println("m2 releaselock"); } /** * m3 synchronized synchronized Service = new Service(); The Service object in; */ public synchronized void m3() { System.out.println("m3 getlock"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m3 releaselock"); } /** * 1. M4 synchronized code block, lock indicates that the current object is locked, i.e. Service Service = new Service(); The Service object in; Same lock as M3; * 2. When thread CD starts at the same time, m3 and M4 methods are synchronized. It can be proved that M3 and M4 are the same lock. * 3. Synchronized can also modify other objects, such as synchronized (service. class), where m4, M1, and m2 methods are synchronized, as evidenced by starting thread ABD. */ public void m4() { synchronized (this) { System.out.println("m4 getlock"); System.out.println("m4 releaselock"); }}}}Copy the code

After the above tests, you may wonder, if the lock exists, where is it stored? Answer: Inside the object. So let’s prove it in code.

Locked inside the object header, an object contains the object header, instance data, and alignment padding. Object headers include MarkWord and object Pointers, which point to the object type of the method area. Instance objects are property data. An object may have many properties, and properties are dynamic. Alignment padding is used to complete the number of bytes. If the object size is not an integer multiple of 8 bytes, the remaining bytes need to be completed, which is convenient for computers to calculate. On 64-bit machines, the object header of an object is 12 bytes of its own size, and on 64-bit operating systems it is 4 bytes, so MarkWord is 8 bytes.

MarkWord includes the object HashCode, the bias lock flag bit, the thread ID, and the lock identifier. To facilitate testing the contents of object headers, you need to introduce maven OpenJDK dependencies.

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.10</version>
</dependency>
Copy the code
/** * @author: duyang * @date: Created in 2020-05-14 20:21 * @description: Created By: * * Fruit object header is 12 bytes (Markword +class) * int is 4 bytes * * 32-bit machine may be 8 bytes; Public ObjectMemory {public static void main(String[] args) {public static void main(String[] args) { //System.out.print(ClassLayout.parseClass(Fruit.class).toPrintable()); System.out.print(ClassLayout.parseInstance(Fruit.class).toPrintable()); }} /** *Fruit testclass */ public class Fruit {private Boolean flag; }Copy the code

Test results: The three lines highlighted below represent object headers, instance data, and alignment padding. The object header is 12 bytes, the instance data Fruit object has a Boolean field flag that is 1 byte in size, and the remaining 3 bytes are aligned with the fill portion for a total of 16 bytes.

Yi? Where are you talking about locks? Why don’t you see them? Don’t worry, lad, we’ll break it down later when we get to the optimization aspects of synchronized. Let’s look at what synchronized means at the JVM level.

Finally, graphic summary:

Synchronized JVM level

/** * @author: jiaolian * @date: Created in 2020-12-20 13:43 * @description: Created in 2020-12-20 13:43 Public static void main(String[] args) {synchronized (syncjvmtest.class) {public static void main(String[] args) {synchronized (syncjvmtest.class) { System.out.println(" JVM synchronization test "); }}}Copy the code

In the example above, we simply output a sentence in a synchronized code block, and we’ll focus on seeing how it is implemented in the JVM. We decompile the above code with Javap -v SyncjVMtest.class, as shown in the figure below.

There is a Monitorenter in the first line and a Monitorexit in the sixth. The JVM directives in the middle (lines 2-5) correspond to the Main method in the Java code that synchronized relies on. Let’s look at the Monitorenter semantics in the JVM specification.

  1. Each object has a lock. When a thread enters a block of synchronized code, it acquires the lock on the monitor object held by that thread (C++). If the current thread acquires the lock, it increments the number of monitor objects entered by one time.
  2. If the thread enters repeatedly, the monitor object is incremented by one more time.
  3. When other threads enter, they are queued until the thread that acquired the lock releases the lock by setting the entry count of the monitor object to 0.

Optimization of Synchronized

Synchronized is a heavyweight lock, mainly because thread contention lock will cause the switch between user mode and kernel mode of the operating system, wasting resources and not high efficiency. Before JDK1.5, synchronized did not do any optimization, but in JDK1.6 performance optimization, it will undergo bias lock, lightweight lock, In jdk1.7, ConcurrentHashMap implemented ReentrantLock, but was replaced with synchronized after jdk1.8. This shows how confident the JVM team is in synchronized’s performance. Below we respectively introduce lock – free, partial lock, lightweight lock, heavyweight lock. Let me draw a diagram to illustrate the state of the locks stored in the object header. As shown in the figure.

  • Without the lock. Synchronized does not use the keyword synchronized.
  • Biased locking.
  • Upgrade process: When a thread enters the synchronization block, Markword will store the ID of the biased thread and CAS will mark the Markword lock status as 01. Biased or not, 1 means that it is currently in the biased lock (as shown in the figure above). If the biased thread enters the synchronization code next time, just compare whether the thread ID of Markword is equal to the current thread ID. If the lock is equal, it can be entered into the synchronization code without any operation. If the lock is not equal after comparison, it indicates that other threads compete for the lock, and synchronized is upgraded to lightweight lock. In this process, there is no need to switch between kernel mode and user mode at the operating system level, which reduces the resource consumption caused by switching threads.
  • Bulking process: Bias locks are upgraded to lightweight locks when another thread enters. For example, if thread A is A biased lock, and thread B enters, it will become A lightweight lock, and as long as there are two threads, it will upgrade to A lightweight lock.

Let’s look at the biased lock state in code.

package com.duyang.base.basic.markword; import lombok.SneakyThrows; import org.openjdk.jol.info.ClassLayout; /** * @author: jiaolian * @date: Created in 2020-12-19 11:25 * @description: Modified By: */ public class MarkWordTest {private static Fruit Fruit = new Fruit(); public static void main(String[] args) throws InterruptedException { Task task = new Task(); Thread threadA = new Thread(task); Thread threadB = new Thread(task); Thread threadC = new Thread(task); threadA.start(); //threadA.join(); //threadB.start(); //threadC.start(); } private static class Task extends Thread { @SneakyThrows @Override public void run() { synchronized (fruit) { System.out.println("==================="+Thread.currentThread().getId()+" "); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(ClassLayout.parseInstance(fruit).toPrintable()); }}}}Copy the code

The code above starts thread A, and the console output is shown in the figure below. The three bits marked in red are 101, respectively. The high 1 indicates biased lock, and 01 indicates biased lock identifier. Conforms to the case of bias lock identifier.

  • Lightweight lock.
  • Upgrade process: After the thread runs and obtains the lock, it will create the lock record in the stack frame and copy the MarkWord to the lock record, and then point the MarkWord to the lock record. If the current thread holds the lock, other threads enter again, at this time, other threads will cas spin until the lock is obtained. Lightweight lock is suitable for multiple threads to execute alternately, with high efficiency (CAS only consumes CPU, I covered this in detail in my article on cas Principles. .
  • Bulking process: There are two cases of bulking into heavyweight locks. The first case is when THE CAS spins 10 times and does not acquire the lock. In the second case, other threads are acquiring the cas lock, a third thread is competing for the lock, and the lock expands to a heavyweight lock.

Below we code to test the lightweight lock state.

Open line 23-24 and execute threadA and thread B. My purpose is to execute threadA and thread B sequentially, so I execute threada.join () in the code first, so that threadA completes its execution first, and then execute thread B, as shown in the picture below. Thread B becomes the lightweight lock, and the lock state becomes 00, which is the lightweight lock state. That’s it.

  • Heavyweight lock. Heavy locks are not reversible, meaning they can no longer be changed to light locks.

Open 25 lines of code, execute threadA, thread B, thread C, my purpose is to execute threadA first, execute threada.join () in the code, so that A line is finished first, and then execute thread BC at the same time, as shown in the following picture to see the change of MarkWord lock status. ThreadA starts with partial lock, and then executes thread BC at the same time. The second condition of lightweight lock inflation is because there is a fierce competition. When another thread is trying to acquire the lock from CAS and a third thread is trying to acquire the lock, the lock will also expand into a heavyweight lock. The BC thread lock status becomes 10, which is consistent with the heavyweight lock status. Expansion heavyweight lock proven.

So far, we have put the synchronized lock upgrade process lock status through the form of code are proved again, I hope to help you. The figure below is a self-summary.

conclusion

Synchronized has always been an important topic and a common topic in interviews.

Source: www.tuicool.com/articles/bY…