Click “like” to see, form a habit, wechat search “Three Prince Aobing” the first time to read.

This article has been included in GitHub github.com/JavaFamily, there are a line of large factory interview complete test sites, information and my series of articles.

preface


Multithreaded things a lot, also very interesting, so my recent focus may be the direction of multithreading to rely on, do not know if you like it?

Read the following two articles before reading this article to help you understand better:

Volatile

Optimistic lock & Pessimistic lock

The body of the

scenario

Synchronized is generally used in the following situations:

  • Modifier instance method that locks the current instance object this

    public class Synchronized {

        public synchronized void husband(a){



        }

    }

    Copy the code
  • Modifies static methods that lock the Class object of the current Class

    public class Synchronized {

        public void husband(a){

            synchronized(Synchronized.class){



            }

        }

    }

    Copy the code
  • Modifies a code block to specify a locked object and lock the object

    public class Synchronized {

        public void husband(a){

            synchronized(new test()){



            }

        }

    }

    Copy the code

Lock method, lock code block and lock object. How do they implement lock?

Before we do that, LET me talk to you about the composition of our Java objects

In the JVM, objects are divided into three areas of memory:

  • Object head

    • Mark Word: Default store object HashCode, generational age, and lock flag bit information. It reuses its own storage space based on the state of the object, which means that the data stored in Mark Word changes as the lock flag bit changes during runtime.
    • Klass Point: A pointer to an object’s class metadata that the virtual machine uses to determine which class the object is an instance of.
  • The instance data

    • This part is mainly to store the data information of the class, the information of the parent class.
  • The filling

    • Since the virtual machine requires that the object’s starting address be a multiple of 8 bytes, the padding data does not have to exist, just for byte alignment.

      Have you ever been asked how many bytes an empty object takes? It is 8 bytes, because of the alignment of the padding, less than 8 bytes to fill it will help us automatically complete.


What about ordering, visibility, atomicity — synchronized?

order

As I mentioned in Volatile, the CPU will reorder our program to optimize our code.

as-if-serial

No matter how the compiler and CPU reorder, it is important to ensure that the results of the program are correct in single-threaded cases, and that data dependencies cannot be reordered.

Such as:

int a = 1;

int b = a;

Copy the code

You can’t reorder either of these, because b depends on A, and a is empty if it’s not assigned first.

visibility

Also in Volatile, I introduced the memory structure of modern computers, and the Java Memory Model (JMM). It should be noted that the JMM does not exist, but is a set of specifications that describe the rules for accessing variables (thread-shared variables) in many Java programs. As well as the low-level details of storing variables into and reading variables from memory in the JVM, the Java memory model is the rule and guarantee for visibility, ordering, and atomicity of shared data.

For those of you interested, remember that understanding the components of a computer, CPU, memory, multilevel cache, etc., will help you better understand why Java does what it does.


atomic

In fact, he’s making sure that atomicity is simple, that only one thread at a time can get the lock, can get into the code block and that’s all that matters.

These are some of the most common features of synchronized. But what about synchronized?

reentrancy

Synchronized locks have a counter that records the number of times a thread acquires the lock. After executing the corresponding code block, the counter is -1 until the counter is cleared and the lock is released.

So what are the benefits of reentrant?

This avoids some deadlocks and allows us to better encapsulate our code.

uninterruptability

Uninterruptible means that after one thread obtains the lock, another thread is in the blocking or waiting state. The first thread is not released, and the second thread is also always blocking or waiting, and cannot be interrupted.

It is worth mentioning that the tryLock method of Lock can be interrupted.

The underlying implementation

Here it looks very simple, I wrote a simple class, lock method and lock code block, let’s decompile the bytecode file, and that’s it.

Take a look at the test class I wrote:

/ * *

 *@Description: Synchronize

 *@Author: AoBing

 *@date2020-05-17:

* * /


public class Synchronized {

    public synchronized void husband(a){

        synchronized(new Volatile()){



        }

    }

}

Copy the code

Javap -c XXX. Class = javap -c XXX. Class = javap -c XXX.

MacBook-Pro-3:juc aobing$ javap -p -v -c Synchronized.class

Classfile /Users/aobing/IdeaProjects/Thanos/laogong/target/classes/juc/Synchronized.class

  Last modified 2020-5-17; size 375 bytes

  MD5 checksum 4f5451a229e80c0a6045b29987383d1a

  Compiled from "Synchronized.java"

public class juc.Synchronized

  minor version: 0

  major version49:

  flagsACC_PUBLIC.ACC_SUPER

Constant pool:

# 1
= Methodref          #3#.14         // java/lang/Object."<init>":()V

   #2 = Class              #15            // juc/Synchronized

   #3 = Class              #16            // java/lang/Object

   #4 = Utf8               <init>

   #5 = Utf8               ()V

   #6 = Utf8               Code

   #7 = Utf8               LineNumberTable

   #8 = Utf8               LocalVariableTable

   #9 = Utf8               this

  #10 = Utf8               Ljuc/Synchronized;

  #11 = Utf8               husband

  #12 = Utf8               SourceFile

  #13 = Utf8               Synchronized.java

  #14 = NameAndType        #4: #5          // "<init>":()V

  #15 = Utf8               juc/Synchronized

  #16 = Utf8               java/lang/Object

{

  public juc.Synchronized();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=1, locals=1, args_size=1

         0: aload_0

         1: invokespecial #1                  // Method java/lang/Object."<init>":()V

         4return

      LineNumberTable:

        line 80

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0       5     0  this   Ljuc/Synchronized;



  public synchronized void husband(a);

    descriptor: ()V

    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  / / here

    Code:

      stack=2, locals=3, args_size=1

         0: ldc           #2                  // class juc/Synchronized

         2: dup

         3: astore_1

         4: monitorenter   / / here

         5: aload_1

         6: monitorexit    / / here

         7: goto          15

        10: astore_2

        11: aload_1

        12: monitorexit    / / here

        13: aload_2

        14: athrow

        15return

      Exception table:

         from    to  target type

             5     7    10   any

            10    13    10   any

      LineNumberTable:

        line 100

        line 125

        line 1315

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0      16     0  this   Ljuc/Synchronized;

}

SourceFile: "Synchronized.java"

Copy the code

Synchronization code

You can see a couple of things that I’ve flagged, and I mentioned the object header at the very beginning, which is going to be associated with a Monitor object.

  • When we enter a human method, monitorenter takes ownership of the current object. The count of monitor entries is 1, and the current thread is the owner of the monitor.

  • If you are already the owner of the monitor, if you enter it again, it will add 1 to the number of entries.

  • Similarly, when monitorexit is finished, the number of entries to monitorexit will be -1 until it reaches 0 before it can be held by another thread.

All mutual exclusion, in this case, is to see if you can get ownership of monitor, and once you become owner you get ownership.

Synchronized methods

Have you noticed the special flag bit of the method, ACC_SYNCHRONIZED?

When a method is synchronized, once the method is executed, ACC_SYNCHRONIZED checks for flag bits, and then implicitly calls monitorenter and Monitorexit.

So it comes down to the contention of monitor objects.

monitor

Objectmonitor.hpp virtual machine objectMonitor.hpp virtual machine objectMonitor.hpp Virtual machine objectMonitor.hpp virtual machine

I looked at the source code, and its data structure looks like this:

  ObjectMonitor() {

    _header       = NULL;

    _count        = 0;

    _waiters      = 0.

    _recursions   = 0;  // The number of threads reentrant

    _object       = NULL;  // Store Monitor objects

    _owner        = NULL;  // Holds the owner of the current thread

    _WaitSet      = NULL;  // List of threads in wait state

    _WaitSetLock  = 0 ;

    _Responsible  = NULL ;

    _succ         = NULL ;

    _cxq          = NULL ;  // One-way list

    FreeNext      = NULL ;

    _EntryList    = NULL ;  // List of threads in the block state waiting for a lock

    _SpinFreq     = 0 ;

    _SpinClock    = 0 ;

    OwnerIsThread = 0 ;

    _previous_owner_tid = 0;

  }

Copy the code

This c++ code, I also put in my open source project, you see.

Synchronized source code is the introduction of ObjectMonitor, this piece of interest you can see, anyway, I said above, and we often hear the concept, can find the source code here.


We say familiar lock upgrade process, in fact, is in the source code, called a different implementation to obtain the lock, failure to call a more advanced implementation, the final upgrade completed.

1.5 Heavyweight Locks

Atomic:: cMPxchg_ptr, Atomic:: INC_ptr and other kernel functions. The corresponding threads are park() and upark().

This operation involves switching between user mode and kernel mode. This switch is very expensive, so you can see why there is a spin lock operation. In fact, it is not, you will know.

What about user mode and kernel mode?

The architecture of the Linux system, which everyone has been exposed to in college, is divided into user space (the active space of applications) and the kernel.

All of our programs run in user-space and go into user-run state (user-mode), but many operations may involve kernel running, which is when we go into kernel-run state (kernel-mode).


This process is very complex and involves a lot of value passing. Let me briefly summarize the process:

  1. The user mode puts some data into a register, or creates a corresponding stack, indicating the need for services provided by the operating system.
  2. User mode performs system calls (system calls are the smallest functional unit of the operating system).
  3. The CPU switches to the kernel state and jumps to the corresponding memory specified location to execute the instruction.
  4. The system calls the processor to read the data parameters we previously put into memory and execute the program’s request.
  5. When the call is complete, the operating system resets the CPU to user mode to return the result and execute the next instruction.

So everyone always said that before 1.6, it was a heavy lock. Yes, but the nature of its weight was determined by the process of ObjectMonitor calls and the complex operation mechanism of the Linux kernel. A lot of system resources were consumed, so the efficiency was low.

There are two other things that can happen when you switch between kernel and user: exception events and peripheral interrupts.

1.6 Optimized lock upgrade

That said, it was inefficient, and the authorities knew it, so they did an upgrade, and if you look at the source code I just mentioned, they did an upgrade that was actually very simple, just a few more function calls, but it had to be very clever.

Let’s take a look at the lock upgrade process:


Simple version:


Upgrade Direction:


Tip: Keep in mind that this upgrade process is irreversible, and finally I will explain its impact, involving usage scenarios.

After watching his upgrade, let’s talk about how he did it.

Biased locking

As I mentioned earlier, object headers are made up of a Mark Word and a Klass pointer. Lock contention is a fight for the Monitor object that the object header points to. Once a thread holds the object, the flag bit is changed to 1 and it goes into bias mode. The thread ID is also recorded in the object’s Mark Word.

In this process, the CAS optimistic lock operation is adopted. Every time the same thread enters the VM, no synchronization operation is performed. The +1 flag bit is good.

Biased locking is enabled by default after 1.6, but disabled in 1.5. The parameter to manually enable is xx: -usebiasedlocking =false.


What about biased lock closing, or multiple threads competing for biased lock?

Lightweight lock

Again related to Mark Work, if the object is unlocked, the JVM creates a space in the current thread’s stack frame called the Lock Record, which stores the Mark Word copy of the Lock object, and points the owner in the Lock Record to the current object.

The JVM then uses CAS to try to update the object’s original Mark Word pointer to Lock Record. If the object is locked successfully, the Lock flag bit is changed and the relevant synchronization operation is performed.

If it fails, it will determine whether the Mark Word of the current object refers to the stack frame of the current thread. If it does, it means that the current thread has held the lock of the object. Otherwise, it means that another thread has held the lock.


spinlocks

I mentioned above that switching between user mode and kernel mode in Linux is a very expensive process, which is actually a waiting process for threads to wake up. How can we reduce this cost?

As soon as the resource is available, try successfully until the threshold is exceeded. The default size of the spin lock is 10 times. -xx: PreBlockSpin can be modified.

If all spins fail, upgrade to a heavyweight lock, like the 1.5, and wait to be aroused.


So far I basically synchronized before and after the concept of all talked about, we have a good digestion.

References: High Concurrency Programming, Dark Horse Programmer Handout, Deep Understanding of THE JVM Virtual Machine

Synchronized or Lock?

Let’s look at the differences:

  • Synchronized is the keyword, which is the bottom layer of the JVM that does everything for us, while Lock is an interface, which is a rich API at the JDK level.

  • Synchronized releases locks automatically, whereas locks must be released manually.

  • Synchronized is not interruptible, and Lock may or may not interrupt.

  • Lock tells if a thread has acquired the Lock, whereas synchronized does not.

  • Synchronized locks methods and blocks of code, while Lock locks only blocks of code.

  • Lock Can use read locks to improve multithreaded read efficiency.

  • Synchronized is a non-fair lock. ReentrantLock controls whether a fair lock is synchronized.

One is at the JDK level and the other is at the JVM level, and I think the biggest difference is actually whether we need a rich API, and one of our scenarios.

For example, I am now didi, and I have a rush hour in the morning. I use a lot of synchronized in my code. What’s the problem? Lock upgrade process is irreversible, after the peak we are still a heavyweight lock, that efficiency is not greatly reduced? Isn’t it a good time for you to use Lock?

Scenarios must be considered, and it would be nonsense for me to tell you which one is better, because without the business, all technical discussions are worthless.

I git on the brain map I write every time I will update, you can have a look.

I am Aobing, a tool to survive on the Internet.

The more you know, the more you don’t know, the talent of the [three] is the biggest impetus for the creation of c c, we will see you next time!

Note: If there are any mistakes or suggestions in this blog, please leave comments!


This article is constantly updated, you can search “Santaizi Aobing” on wechat for the first time to read, reply [information] I prepared the interview materials and resume template, GitHub github.com/JavaFamily has included this article, there is a complete test site for the interview of big factory, welcome Star.