The synchronized keyword is certainly familiar to Java developers, and we may be comfortable with its usage, but do we really know much about it? Although there are many articles on the Internet have been the use and principle of synchronized, but I still want to according to my personal knowledge, to talk with the big guys about this keyword. I don’t want to come up with any implementation principles, but let’s take a look at the usage of synchronized, and then talk about the implementation principles of synchronized from shallow to deep, so as to thoroughly grasp it.

One, foreword

We all know that the synchronized keyword is a Lock provided at the Java language level that guarantees order and visibility for code. Synchronized, as a mutex, can only be accessed by one thread at a time. We can also consider the area modified by synchronized as a critical area, where only one thread can access the critical area. When the accessing thread exits the critical area, another thread can access the critical area resources.

C. synchronized d. synchronized

1. How does it work

Synchronized is generally used in two ways: synchronized modifiers and synchronized code blocks.

“Synchronized” is the word used to describe the use of synchronized.

public class TestSynchronized {

    private final Object object = new Object();



    // Decorate static methods

    public synchronized static void methodA(a) {

        System.out.println("methodA.....");

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }



    // synchronized(object)

    public void methodB(a) {

        synchronized (this) {

            System.out.println("methodB.....");

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }



    // synchronized(class)

    public void methodC(a) {

        synchronized (TestSynchronized.class) {

            System.out.println("methodC.....");

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }



    // Modify common law law

    public synchronized void methodD(a) {

        System.out.println("methodD.....");

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    } 

    // Modify the ordinary object

    public void methodE(a) {

        synchronized (object) {

            System.out.println("methodE.....");

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

Copy the code

Our example above contains basically all the uses of synchronized, so let’s run it to see what the print order of the methods looks like.

  1. Synchronized method(){}; synchronized method(){}; synchronized method(){}
 final TestSynchronized obj = new TestSynchronized();

        new Thread(() -> {

            obj.methodB();

        }).start();



        new Thread(() -> {

            //obj.methodB();

            obj.methodD();

        }).start();

Copy the code

Whether two threads call the same method or different methods, we can see by running the code that the console prints methodB….. first Wait a second before printing out the output of a method called by another thread.

Why print methodB first and then print methodD later? Taking a look at the figure below, let’s call different methods as an example.

At the beginning of the article, we also introduced the function of synchronized as a lock, which can also be regarded as a critical region. Through the running results of the code, we can see that methodB…. is printed here first MethodD was printed after a while. We can feel as if the two threads are accessing the same critical section, otherwise the console should print methodB…. almost at the same time And methodD… In this case, we can also run the above example to see the order of printing.

We can also look at the code and say, what does this mean? This refers to the object that calls the method, and synchronized method() is instantiated, so when synchronized method() and synchronized (this) are called in the same instance, We use a critical section, which is what we call the same lock.

Here I think the concept of critical sections should be a little easier to understand. We can think of synchronized method () and synchronized () modified code as critical sections, If the synchronized modified method object is the same object as the argument passed in the synchronized block (and by the same object we mean that their hashCode is equal), then the same critical section is used, otherwise it is not.

  1. So let’s move on to the next one
   final TestSynchronized obj = new TestSynchronized();

   final TestSynchronized obj1= new TestSynchronized();

        new Thread(() -> {

            //obj.methodC();

             obj1.methodC();

        }).start();



        new Thread(() -> {

            //obj.methodC();

            TestSynchronized.methodA();

        }).start();

Copy the code

Whether we call methodC with obj1 or obj, or whether obj calls methodC() and obj1 calls methodC(), we print methodC….. first It takes a second to print another output.

This is the same as using an object, except instead of a Class object. At runtime, the code generates only one Class object. Synchronized (object.class) and statci synchronized method () use object.class as a synchronized method.

Here we do not go to an example, maybe big guys can also know the meaning I want to convey, interested partners can start to run the code, see the result. I’m just going to jump to the conclusion here.

  1. When a thread accesses the synchronized(this) block or synchronized method () method of the same object, or when a thread accesses the synchronized(this) block of the object, In addition, threads accessing synchronized method () are blocked. Only one thread can be executed at a time. Another thread must wait for the current thread to finish executing the code block before executing it.

    When one thread accesses an object’s synchronized (object) code block or synchronized method (), other threads can access the object’s non-synchronized (obj) or synchronized method () methods simultaneously. Note here that for *** the same object ***

  2. When a thread accesses a static synchronized method () modified static method or synchronized () block whose argument is a Classs, Another thread accessing synchronized (object.class) or static synchronized method () is blocked.

    When one thread accesses a static method of a synchronized modifier, other threads can also access a non-static method of another synchronized modifier, or non-synchronized (object.class). Notice that the class must be the same class

    Object.class == object.getClass (); .class represents an object of class. Class is not a Java keyword, but a Class.

2. What problems can be solved

We’ve seen some of the uses of synchronized above, and we’ve actually seen that synchronized solves the visibility and atomicity problems associated with concurrent access to shared variables by multiple threads. In addition, synchronized and wait()/notify() can be used to alternate threads. Let’s take a look at the following examples or pictures.

The following example is the classic classic print ABC problem

public class TestABC implements Runnable{

    private String name;

    private Object pre;

    private Object self;



    public TestABC(String name,Object pre,Object self){

        this.name = name;

        this.pre = pre;

        this.self = self;

    }



    @Override

    public void run(a){



        int count = 10;

        while(count>0) {

            synchronized (pre) {

                synchronized (self) {

                    System.out.print(name);

                    count --;

                    // Release the lock to unlock the condition next time

                    self.notify();

                }

                try {

                    // Lock the previous data

                    pre.wait();

                } catch (Exception e) {

                    // TODO: handle exception

                }



            }



        }



    }



    public static void main(String[] args) throws InterruptedException {

        Object a = new Object();

        Object b = new Object();

        Object c = new Object();



        Thread pa = new Thread(new TestABC("A",c,a));

        Thread pb = new Thread(new TestABC("B",a,b));

        Thread pc = new Thread(new TestABC("C",b,c));

        pa.start();

        TimeUnit.MILLISECONDS.sleep(100);

        pb.start();

        TimeUnit.MILLISECONDS.sleep(100);

        pc.start();

    }

}

Copy the code

Synchronized: Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized

This is a desktop application running on the same lock. There is no way to simulate multiple locks, but we can see the effect of wait()/notify(). When a thread that is executing calls wait(), the thread voluntarily confuses the lock. This can be interpreted as leaving the critical section and allowing other threads to enter the critical section. A thread that calls wait () can only be woken up by notify () and can then resume or take execution of a critical section.

The illustration above better illustrates how the sample code works.

We start threads in order A, B, and C. When thread A finishes, it’s going to wait to wake up in c, step 3 in the upper left corner, and it’s going to wait for thread B to wake up in A, and it’s going to wait for thread C to wake up in B.

When A thread starts in this order has been completed, after the thread scheduling went to A CPU execution order is uncertain, but when A thread after C implementation, awakens in C waiting thread A critical area, and thread B will always be blocked, until the thread is waiting on A critical section is awakened (that is, perform a.n otify ()), Can be executed again. The same goes for the other two threads, which completes the sequential execution of the threads.

The realization principle of synchronized

With that said, we may or may not have a feeling for synchronized. Synchronized is a lock, but how does it work? What exactly does synchronized lock? That’s the main thing we’re going to talk about

The Java memory model provides eight atomic operations. Two of them are lock and unlock. These atomic operations are implemented using the moniterEnter and MoniterExit instructions. Synchronized threads are mutually exclusive through these two instructions, but synchronized modifiers and synchronized code blocks are slightly different, so I’ll take a look at the differences between these two implementations.

Synchronized methods

To decompile our Java code using the Javap command, we can see the bytecode shown below

We didn’t find any clues about the lock in the decompiled bytecode. Don’t worry, let’s decompile through the javap -v command

MethodD () has an ACC_SYNCHRONIZED attribute in the flags attribute.

A reference to the JVM specification for synchronized methods was found: reference 1, reference 2

Its general meaning can be summarized as the following points

  • The implementation of the synchronization method is not based on the Monitorenter and Monitorexit directives
  • At runtime, the constant pool is defined by ACC_SYNCHRONIZED, which is checked during method execution
  • When a method has this flag, the incoming thread first needs a monitor to execute the method

Some details on method_info are given here and can be found in the official documentation.

Synchronized code block

By decompiling our code above, we get methodC’s bytecode as follows.

Here we see the moniterEnter and MoniterExit directives, which the JVM specification describes as “official”

Monitorenter

Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread waits until the object is unlocked,

Each object has a Monitor associated with it, and the thread that executes the MoniterEnter directive gains ownership of the Monitor associated with the Objectref. If another thread already owns the Monitor associated with the Objectref, the current thread will wait until the object is unlocked.

Monitorexit

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

When Monitorexit is executed, the count of the monitor associated with the thread is reduced by one. If the count is zero, the monitor exits and the thread is no longer the owner of the monitor.

The realization of synchronized

The JVM specification also states that each object has a Monitor associated with it, so let’s see how they are related.

Object memory structure

In the HotSpot virtual machine, the layout of objects stored in memory can be divided into three areas: object headers, Instance Data, and Padding.

The object header of the HotSpot VIRTUAL machine object contains two types of information. The first type is runtime data used to store the object itself, such as HashCode, GC generation age, lock status flags, locks held by threads, bias thread ids, bias timestamps, and so on. The other part is a type pointer, a pointer to an object’s type metadata that the Java virtual machine uses to determine which class the object is an instance of.

The lock identifier is stored in Mark Word and is structured as follows.

The pointer corresponding to flag bit 10 points to the Monitor object, which is implemented by ObjectMonitor and its main data structure is as follows (located in the ObjectMonitor. HPP file of the source code of the HotSpot virtual machine, implemented by C++).

ObjectMonitor() {

    _header       = NULL;

    _count        = 0// Number of records

    _waiters      = 0.

    _recursions   = 0;

    _object       = NULL;

    _owner        = NULL;

    _WaitSet      = NULL// Threads in wait state are added to _WaitSet

    _WaitSetLock  = 0 ;

    _Responsible  = NULL ;

    _succ         = NULL ;

    _cxq          = NULL ;

    FreeNext      = NULL ;

    _EntryList    = NULL ; // Threads in the waiting block state are added to the list

    _SpinFreq     = 0 ;

    _SpinClock    = 0 ;

    OwnerIsThread = 0 ;

  }

Copy the code

ObjectMonitor has two queues, _WaitSet and _EntryList, that hold the list of ObjectWaiter objects (each thread waiting for a lock is encapsulated as an ObjectWaiter object). _owner refers to the thread that holds the ObjectMonitor object. When multiple threads simultaneously access a piece of synchronized code, they first enter the _EntryList collection. When a thread obtains the object’s monitor, it enters the _Owner area, sets the owner variable in monitor to the current thread, and increases the count in monitor by 1. If the thread calls wait(), it will release the currently held monitor. The owner variable returns to null, count decreases by 1, and the thread enters the WaitSet collection to be woken up. If the current thread completes, it also releases the monitor(lock) and resets the value of the variable so that another thread can enter to acquire the monitor(lock). As shown in the figure below

From this point of view, a Monitor object exists in the object header of every Java object (the point of the stored pointer), and synchronized locks are mutually exclusive in this way.

conclusion

Above, we have analyzed the basic use of synchronized and the realization principle of synchronized. We should have a general grasp of the keyword of synchronized. If a friend is interested in the small program, you can leave a message to me and I can share it with everyone. You can feel it yourself. I hope you can give me a thumbs up.

Reference:

In-depth Understanding of the Java Virtual Machine

https://www.jianshu.com/p/7f8a873d479c

https://blog.csdn.net/jinjiniao1/article/details/91546512