Hello everyone, I am wave, this time to talk with you in detail about the keyword Synchronized, I hope you can have a very comprehensive understanding of the keyword Synchronized.

Synchronized basic operation

Synchronized can be used in three main ways:

Accessorize instance methods: Add the synchronized keyword to a method on a class. This lock is applied to the current instance object of the class and is acquired before entering the synchronized code

public synchronized void method(a){
        // do something
    }
Copy the code

Modify static methods: Because static methods are classes, they lock a class and apply the lock to all object instances of the class before entering the synchronized code

public synchronized static void method(a){
        // do something
    }
Copy the code

Modifies code block: Specifies the lock object and locks the given object/class. Synchronized (this | object) said before entering the synchronization code base for a given object lock. Synchronized (class. Class) means acquiring the lock of the current class before entering the synchronized code

public class Solution {
    public void method(a){
        synchronized (this) {// Lock the current object
        }
        synchronized (Solution.class){
            // Lock the Solution class
        }
        Object lock = new Object();
        synchronized (lock){
            // Lock the Object}}}Copy the code

Synchronized was a very heavy weight Lock in the early JDK, relying on JVM instructions to Lock code blocks, so synchronized is the JVM level Lock, and Lock is implemented in Java code, so it can be regarded as the API level Lock. However, there have been numerous subsequent JDK optimizations and upgrades to synchronized, and synchronized is now almost as efficient as Lock.

Synchronized modifies code blocks

Synchronized modify-code blocks are implemented using monitorenter, monitorexit, and monitorenter

public class Solution {
   public void method(a){
       synchronized (this){
           System.out.println("Synchronized modifies code blocks"); }}}Copy the code
  • Write a piece of such code first, and then you can view its bytecode through Javap. If you use IDEA, you can directly output bytecode on the console through a little configuration
  • File->setting->Tools->External-> top left +
  • $JDKPath$\bin\javap.exe
  • -verbose -p -c $FileClass$
  • $OutputPath$
  • Then compile the program, press the small green hammer next to run, right-click External Tool, find the tool you just configured, and print out the bytecode on the console.

Synchronized is added to a block of synchronized code using JVM instructions. When the JVM executes a monitorenter, the thread attempts to acquire the lock that holds the object’s monitor.

Synchronized modification method

public class Solution {
    public synchronized void method(a){
        System.out.println("Synchronized modification"); }}Copy the code

You can see that modifying a method has an ACC_SYNCHRONIZED identifier that indicates that the method is synchronized.

Synchronized lock escalation

Synchronized was greatly improved in JDK1.6, and now comes in three forms: bias, lightweight, and heavyweight locks. These three types of locks can be adapted to different application scenarios. When there is almost no concurrency, the lock efficiency is higher than lightweight lock and heavyweight lock. When the concurrency is not very high, the lock is upgraded to lightweight lock, and the efficiency of lightweight lock is higher than heavyweight lock.

  • What are bias locks, lightweight locks, and heavyweight locks?
  • Biased locking: I think the key to biased locking is biased, favoring the first access thread. In other words, in an uncontested environment, if there is a synchronized code block accessed by a thread, the lock will be biased towards this thread, and the next time a thread accesses it, it will determine whether it is accessed by the previous thread, thus reducing the cost of cas. The first time a thread accesses a block of synchronized code, cas writes the thread ID to the Mark Word. Biased locking has a delay. Biased locking doesn’t happen for the first 5 seconds of the program, which I’ll prove to you in a second. Biased locking doesn’t happen on objects that have computed hashCode, because there’s no room for thread ids in their headers.
  • Lightweight lock: The point of lightweight lock is its spin. If a thread accesses the synchronization code block of lightweight lock, the CAS will determine whether the thread ID is consistent. If the thread id is inconsistent, the CAS will spin for a certain period of time. If that fails, then the lightweight lock is upgraded to the heavyweight lock.
  • Heavyweight locks: Two tokens at the JVM level that block other threads when unlocked.
  • The Cas operation takes three arguments, the address of the old value, the old value, and the new value. If the value fetched from the old value address is equal to the old value, the old value is changed to the new value.
  • Mark Word is a part of the object header, which mainly includes Mark Word and Klass Word. Mark Word usually stores information such as thread ID, garbage collection life and lock status. Klass Word holds a pointer to an instance of an object, which is the address in the heap. This pointer is usually 64-bit, but is usually compressed to 32bit.

  • From the above figure, we focus on the last two bits, which represent the lock state. So an object can be in five states: unlocked, biased locked, lightweight locked, heavyweight locked, and in GC (indicating that garbage collection is in progress).
  • In theory, two bits can only represent four states, so why five? Careful friends can find that the bit before the biased lock is also used to indicate the lock state, that is to say, there is another 1bit used to judge whether the lock is biased.
  • So no lock is 01, lightweight lock is 00, GC is 11, heavyweight lock is 10, biased lock is 101

  • This is a picture of the lock upgrade widely circulated on the Internet. If you can understand this picture, it will definitely impress the interviewer. However, personally, this picture is still a little difficult to understand. If you want to know more about the lock upgrade process, just look at this image. Of course, you can also leave a message on the public account: Lock upgrade picture)

JOL prints the object header

What kind of situations does synchronized upgrade? In Java, there is a JAR package called Jrol-core that can be used to print the object header of an object using the API.

unlocked

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;

public class JOLExample1 {
    public static void main(String[] args) throws Exception {
        A a = newA(); out.println(VM.current().details()); out.println(ClassLayout.parseInstance(a).toPrintable()); }}Copy the code

  • In this example, object A does not have any lock, so it must be unlocked. As can be seen from the console print, the VALUE of VALUE is the VALUE of the object header. The first two characters are printed as 01, which is indeed unlocked. So, am I kidding you
  • The last two digits of the object’s head represent the lock state. Why are the first two digits of the printed object represent the lock state?
  • There’s a concept in computers called big-endian mode and small-endian mode, and most computers today are small-endian mode, where the lower bits of word data are stored at the higher addresses, and that’s where this result comes in.

Biased locking

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
// There is no competition
public class JOLExample2 {
    static A a;
    public static void main(String[] args)throws Exception{
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        sync();
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    public static void sync(a) throws InterruptedException {
        synchronized (a){
            System.out.println("I don't know what to print."); }}}Copy the code

It can be seen that although synchronized is added to A in the above example, there is no competition. According to our analysis, it is biased lock, so will it be 101 printed out?

  • The magic discovery is again 01, which means no lock. Why is that? This is because there is a 4s delay for biased locking, so if you change the vm parameters or sleep for 5s, you can see 101
  • (XX: XX: + UseBiasedLocking – BiasedLockingStartupDelay = 0)
public class JOLExample2 {
    static A a;
    public static void main(String[] args)throws Exception{
        Thread.sleep(5000);// Stop for 5 seconds
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        sync();
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    public static void sync(a) throws InterruptedException {
        synchronized (a){
            System.out.println("I don't know what to print."); }}}Copy the code

See? The long-awaited bias lock is finally out

Lightweight lock

public class JOLExample3 {
    static A a;
    public static void main(String[] args)throws Exception{
        Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
         new Thread(()->{
            sync();
        }).start();
         Thread.sleep(5000);// Ensure that the thread above completes
        out.println("after lock");
        //out.println(ClassLayout.parseInstance(a).toPrintable());
        sync();
        out.println("lock over");
        out.println(ClassLayout.parseInstance(a).toPrintable());

    }
    public static void sync(a) {
        synchronized (a){
            try {
                out.println(ClassLayout.parseInstance(a).toPrintable());
            }catch (Exception e){
            }

        }
    }
}
Copy the code

As we can see from the graph, a thread that locks a still prefers the lock because there is no race. When another thread locks a, it becomes a lightweight lock (note that there is still no contest), and when it exits a synchronized block, it becomes unlocked again, because a lightweight lock is revoked.

Heavyweight lock

public class JOLExample3 {
    static A a;
    public static void main(String[] args)throws Exception{
        Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
         new Thread(()->{
            sync();
        }).start();
         //Thread.sleep(5000); // Ensure that the thread above completes
        out.println("after lock");
        //out.println(ClassLayout.parseInstance(a).toPrintable());
        sync();
        out.println("lock over");
        out.println(ClassLayout.parseInstance(a).toPrintable());

    }
    public static void sync(a) {
        synchronized (a){
            try {
                out.println(ClassLayout.parseInstance(a).toPrintable());
            }catch (Exception e){
            }

        }
    }
}
Copy the code

Simply annotating sleep 5S in the previous lightweight lock example creates a two-thread race, where the main thread is competing with the newly created thread. As you can see, the printed result is 10, indicating the heavyweight lock.

An underlying implementation of synchronized

  • Let’s start by looking at what parts it’s made of
  • Wait Set: This is where threads whose Wait calls are blocked are placed
  • Contention List: Contention queue in which all threads requesting locks are placed first
  • 1. Those eligible for contention will be moved to the contention list
  • Ondeck: At most one thread, ondeck, is competing for resources at any one time
  • Owner: indicates the thread that obtains the resource
  • More on the general workflow of Synchronized
  • The JVM fetches data one at a time from the end of the queue for lock contention candidates (onDecks), but in the case of concurrency, the ContentionList is CAS accessed by a large number of concurrent threads. To reduce contention on tail elements, The JVM moves a subset of threads into the EntryList as candidate contention threads.
  • The Owner thread migrates some of the ContentionList threads into the EntryList when unlock, and designates one of the EntryList threads as the OnDeck thread (usually the first thread to enter).
  • Instead of passing the lock directly to the OnDeck thread, the Owner thread gives the lock contention to OnDeck, which needs to recontest the lock. This sacrifices some fairness, but can greatly improve the throughput of the system. In the JVM, this choice behavior is also called “competitive switching.”
  • The OnDeck thread becomes the Owner thread after acquiring the lock resource, while the thread that does not acquire the lock resource remains in the EntryList. If the Owner thread is blocked by wait, it is placed in the WaitSet queue until it is awakened by notify or notifyAll and re-entered into the EntryList. The ContentionList, EntryList, and WaitSet threads are all blocked by the operating system (pthread_mutex_lock in Linux).
  • Synchronized is an unfair lock. Synchronized when a thread enters the ContentionList, the waiting thread will first try to acquire the spin lock. If it fails to acquire the lock, it will enter the ContentionList, which is obviously unfair to the thread that has entered the queue. Another unfair thing is that the thread that spins to acquire the lock may also directly preempt the lock resource of the OnDeck thread.
  • Each object has a Monitor object. Locking competes with the monitor object by adding monitorenter and Monitorexit directives before and after the block. Synchronized is a heavyweight operation that needs to call the relevant interface of the operating system. Its performance is inefficient, and it may consume more time to lock the thread than to use the useful operation.

ENDING

I hope you can use the knowledge learned in this article to “attack” the interviewer when they ask you about synchronized.

This is the end of the sharing, for such a dry article does not like your heart will not hurt ~


The learning group is established

  • Into the group without losing the union ~

Phase to recommend

  • Java Personal Learning path -Laochou
  • How ugly people made their first money in college
  • Did you get offers from Alibaba and Tencent when you were an undergraduate?
  • What did you experience when you got 30W annual salary as an undergraduate?