Detailed explanation of Synchronized keyword in java concurrency

Synchronized concept

  • It is mainly used for synchronization in java concurrency
  • This keyword can ensure that the method or code block modified by it can only be executed by one thread at any time
  • It can use any java object as a lock to achieve synchronization

Synchronized usage

There are three main ways to use

  • Modification example method
synchronized void method() {
    
    
  //业务代码
}

Use the current instance object as the lock

  • Decorate static methods
synchronized void staic method() {
    
    
  //业务代码
}

Use the class object of the current class as the lock (here the class object and the instance object are separate)

  • Modified code block
synchronized(this) {
    
    
  //业务代码
}

Here is the object designated as the lock by the user. Of course, it can be designated as the current instance object, this, which is very similar to the modified instance method; of course, it can also be designated as the class object, namely xxx.class, then it is statically modified at this time. The method is very similar.

Why objects can be used as locks

After a brief introduction to the concept and usage of Synchronized, we need a pre-shop knowledge, that is, why java objects can be used as locks, and how to use them as locks.

First of all, the structure of java objects in memory is like this:

Insert picture description here
It is divided into object header, instance data, and alignment padding.
(The head of the object; some data of the object itself; the object data is not enough, in order to meet the standard of JVM, so as to fill in the data to complete)

Synchronized with locks is present in java object head inside.
The structure of the java object header is divided into three parts:

  • mark word: the hashcode or lock information of the storage object, and the generation age (GC related)
  • class metadata address: a pointer to the data of the object type (pointer to the class object, indicating what type of the object is)
  • array length: array length (if it is an array)

The structure of the mark word is as follows: (This includes the following 32-bit unified look, too lazy to look at 64-bit, they are almost the same)
Insert picture description here

In general, the markword in the object header stores information about the lock of the object, and the type and status of the object lock depends on the value of the mark word here.

Synchronized underlying implementation

(This is for the implementation of Synchronized before jdk1.6 and heavyweight locks after jdk1.6. This is meant to be distinguished from biased locks and lightweight locks, and they do not use monitor) For
code blocks and methods, Synchronized The implementation of is not the same, here needs to be distinguished.

Code block

  • If you write a demo yourself, and then use javap to view the bytecode instructions, you can find
  • Before and after the synchronization code block, there are monitorenter and monitorexit instructions, which are added by jvm for me.
  • When the monitorenter instruction is executed, the thread tries to acquire the lock, that is, to acquire the right to hold the object monitor monitor
  • In the Java Virtual Machine (HotSpot), Monitor is implemented based on **C++** and implemented by ObjectMonitor.
  • When a lock (weight lock) is acquired, a pointer to the monitor object will be stored in the markword.
    During execution monitorenter, it will try to acquire the lock of the object. If the lock counter is 0, the lock can be acquired. After acquiring the lock counter, set the lock counter to 1 means adding one.
    In the implementation of monitorexitthe directive, the lock counter is set to 0, indicating that the lock is released. If acquiring the object lock fails, the current thread will block and wait until the lock is released by another thread.
  • (In addition, the wait/notifyequivalent method also depends on the monitorobject, which is why the equivalent method can only be called in a synchronized block or wait/notifymethod, otherwise java.lang.IllegalMonitorStateExceptionan exception will be thrown . )

In general, JVM added two instructions for us to let the thread get the monitor object implemented by C++. If it gets it, put the pointer of the object in the markword to get the lock completed (weight lock)

method

  • If you write a demo yourself, and then use javap to view the bytecode instructions, you can find
  • synchronizedModified method does not monitorentercommand and monitorexitcommand, made on behalf of the really ACC_SYNCHRONIZEDidentifier, which indicates that the method is a synchronous method. JVM through the ACC_SYNCHRONIZEDaccess flag to identify whether declared as a method of synchronization methods to perform the appropriate synchronization calls.

The essence of both is the acquisition of the object monitor monitor. Essentially the same

Synchronized optimization

Before jdk1.6, Synchronized was a very heavy keyword.
The early Synchronized was a heavyweight lock, a pessimistic lock.

Because the monitor described above is implemented by relying on the Mutex Lock of the underlying operating system, Java threads are mapped to the native threads of the operating system. If you want to suspend or wake up a thread, you need the help of the operating system to complete it, and the operating system needs to switch from user mode to kernel mode when switching between threads. The transition between this state requires a relatively long time and time cost. Relatively high.

In fact, in our application scenarios, such a heavy lock is not needed every moment.
In many cases, we will have a very long time, only one thread accesses the same synchronization code block; there will also be situations where multiple threads alternately access the synchronization block; of course, there will also be multiple threads competing for synchronization code blocks at the same time. situation.
For different situations, if we use a single weight lock to solve the problem, then the efficiency is too low.

Therefore, jdk1.6 has greatly optimized synchronized at the JVM level, so the current synchronized lock efficiency is also optimized very well. JDK1.6 introduces a large number of optimizations to the implementation of locks, such as spin locks, adaptive spin locks, lock elimination, lock coarsening, biased locks, lightweight locks and other technologies to reduce the overhead of lock operations.

Talk about the lock in java

(After jdk1.6)
This is also the realization of the Synchronized principle.
There are four lock types defined in the markword of the java object header:

  • No lock state
  • Biased lock state
  • Lightweight lock state
  • Heavyweight lock status

Why it is divided into this state has been mentioned in the previous section.

These four types are graded from low to high and are one-way. The lock can only be relegated or upgraded, and cannot be downgraded.

In the markword, various types of locks are defined in
Insert picture description here
this way. The following table shows the information recorded in the markword under different lock states. (Very critical). The weight lock stores a pointer to the monitor.
Insert picture description here

Bias lock

  • Used to solve the scenario where only one thread accesses the synchronized block
  • The biased lock will not be released actively (when will it be released? When there is competition)
  • In most cases, the same thread enters the same code block, so there is no need to acquire and release locks in this situation
  • Let's talk about the acquisition and upgrade process of biased locks:
  1. Thread A accesses whether the identification of the biased lock in the markword is 1, and whether the lock identification bit is 01-to confirm that the bias can be filled
  2. If it is a biasable state, test whether the thread ID points to the current thread A, if it is, go to step 5, otherwise go to step 3.
  3. If the thread ID does not point to the current thread A, the CAS operation will compete for the lock. If the competition succeeds, set the thread in the markword to the current thread ID, obtain the bias lock successfully, and then perform step 5, if it fails, perform step 4
  4. If CAS fails to acquire a biased lock, it means that there is contention. At this point, it means that another thread B has already obtained the bias lock. (Because the biased lock will not be released actively), the current thread A does not know whether the thread B holding the biased lock is still running. Therefore, when the global safety point is reached (the point in time when there is no bytecode instruction execution), thread B holding the bias lock is suspended. The jvm checks whether the thread B holding the biased lock is alive . If it dies, the lock becomes unlocked, and then re-biased to the new thread; if it is alive, the stack with the biased lock is executed, and the current lock is upgraded to lightweight Level lock. (Reversing the bias lock will cause STW)

In general, as long as more than one surviving thread accesses the same synchronization area, it will generally be upgraded to a lightweight lock, and the biased lock is only suitable for single-threaded scenarios

Lightweight lock

  • Used to solve the scenario where multiple threads alternately access synchronized code blocks.
  • For example, after thread A finishes executing, it exits the synchronization area, and then thread B enters the synchronization area. At this time, it is a lightweight lock.
  • For this kind of alternate access with more than one thread, it is obvious that a heavy weight lock is not required, so a lightweight lock is used to solve it.
  • Let's talk about the acquisition and release process of lightweight locks:
    • Before thread A enters the synchronization block, jvm will help copy the data of the markword of the lock object header (called the displaced
      markword) to the lock record in the stack frame of thread A
    • Then thread A tries to use CAS to replace the markword of the lock object header with a pointer to its own lock record (competition lock, lock).
      If it succeeds, it acquires a lightweight lock.
      If it fails, it continues to spin and try CAS (consumption CPU, but only once, when the thread holding the lock releases the lock, it will expand into a heavyweight lock. At the same time, spin has a threshold, if it exceeds this threshold, it will be directly upgraded to a weight lock)
    • Unlock: When unlocking, try to copy the Displaced mark word in the lock record back to the object header (restore the previous state).
      If it fails, it means there is competition, and it is expanded to a heavyweight lock .
      If successful, there is no need to upgrade (of course it is impossible to downgrade)

Heavyweight lock

  • In fact, in the Synchronized section above, we have already talked about the implementation of heavyweight locks, which are implemented through instructions, monitor objects, etc.
  • A pointer to the monitor object is stored in markword
  • After thread A holds the heavyweight lock, other locks will be blocked when they access the synchronization area. After thread A releases the lock, they will be awakened, and then they will compete again.

Except that it can return to the unlocked state in the biased lock state, the others cannot be downgraded.

Add a question: Where is the hashcode placed when it is locked?
When I was looking at concurrency, I was also considering this issue. Isn't there no place to put hashcode?
After querying, I found the answer:

  • When an object has calculated the identity hash code, it cannot enter the biased lock state;
  • When an object is currently in a biased lock state, and its identity hash code needs to be calculated, its biased lock will be revoked, and the lock will expand into a weight lock;
  • In the implementation of weight lock, there is a field in the ObjectMonitor class that can record the mark word in the non-locked state, and it can store the value of the identity hash code. Or simply put, the weight lock can store the identity hash code.
  • The hash codes discussed here are only for identity hash codes. The value returned by the user-defined hashCode() method is not the same as discussed here. Identity hash code is the value returned by java.lang.Object.hashCode() or java.lang.System.identityHashCode(Object) that has not been overwritten.

Reference

RednaxelaFX knows the answer, where is the hashcode the
cornerstone of Java concurrency-the so-called "blocking": Object Monitor and AQS (1)
knows the answer that users know why java locks expand each other locks in
java-biased locks, lightweight Locks, spin locks, heavyweight locks
15. The 4 states of locks in multithreaded programming-lock-free state biased to lock state, lightweight lock state, heavyweight lock state,
and concurrent part of javaGuide

Guess you like

Origin blog.csdn.net/qq_34687559/article/details/114164849