Synchronized is no longer bloated, let go of his prejudice when he first knew lightweight locks

foreword

  • Natural selection, survival of the fittest. JDK is also constantly being optimized. Regarding the internal synchronization of the synchronized lock in the JDK, it is also continuously optimized. Earlier, we analyzed the biased lock to solve the initial problem, and the lightweight lock operation was born with the continuous accumulation of contention.
  • Follow me, a progressive social farmer, take you out of the crisis

Lightweight lock

  • As mentioned above, the biased lock will only be generated when there is no competition and the biased lock is turned on. However, the biased lock will not be automatically revoked. Let's look at the following case
  • The vm configuration is as follows-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
 public class SimpleTest {
     public static void main(String[] args) {
         SimpleTest test = new SimpleTest();
         System.out.println(ClassLayout.parseInstance(test).toPrintable());
         synchronized (test) {
             System.out.println("hello world");
             System.out.println(ClassLayout.parseInstance(test).toPrintable());
         }
         System.out.println("锁释放后:"+ClassLayout.parseInstance(test).toPrintable());
 ​
     }
 }
复制代码
  • We can see that the marks in the test objects of the three processes are always biased before locking, during locking, and after locking. This means that it will not be voluntarily revoked

image-20211213143811117.png

  • Based on this premise, let's imagine that two threads lock on the same object at different times. Is this called resource competition? Because it is actually interactive during not running at the same time, but because the biased lock is not actively released by default. In the biased lock locking process, the current thread is written to the mark through CAS. Before writing, it is compared whether the mark of the lock object is the current thread. If it is consistent with the current thread id, only 1 will be added to the counter to implement reentrant locks.
  • If it is the second thread, thread id inconsistency will occur regardless of whether it is at the same time or not. At this time, the biased lock will be upgraded to a lightweight lock. This upgrade process is also a very troublesome process. The JVM actually needs to find a safe point (that is, the thread inactivity time point), first revoke the bias lock, and then put the lightweight lock on

Bias lock icon

image-20211213145511987.png

Lightweight lock icon

image-20211213150317351.png

  • We can also see from the diagram that the biased lock will only occur CAS once, while the lightweight lock will occur CAS all the time. We need to know that the thread spin caused by CAS also consumes CPU scheduling, because the threads are all active , then the CPU will have thread scheduling switching. So it's funny to favor locks in projects where concurrency is not very high and common.
 ​
 class User{
     String userName;
 }
 public class SoftLock {
     public static void main(String[] args) throws InterruptedException {
         User user = new User();
         System.out.println("加锁前(禁用偏向延迟,此时应该是偏向锁默认):"+ClassLayout.parseInstance(user).toPrintable());
         final Thread t1 = new Thread(new Runnable() {
             @Override
             public void run() {
                 synchronized (user) {
                     System.out.println("t1加锁中:" + ClassLayout.parseInstance(user).toPrintable());
                 }
             }
         });
         t1.start();
         t1.join();
         final Thread t2 = new Thread(new Runnable() {
             @Override
             public void run() {
                 synchronized (user) {
                     System.out.println("t1加锁中,因为t1加锁后线程偏向锁不会释放,所以t2会发生偏向锁撤销,最终t2轻量级锁:" + ClassLayout.parseInstance(user).toPrintable());
                 }
             }
         });
         t2.start();
         t2.join();
         System.out.println("加锁后(无锁):"+ClassLayout.parseInstance(user).toPrintable());
     }
 }
复制代码
  • We can see from the above code that trying to lock in the t2 thread will become a lightweight lock. The difference between a lightweight lock and a biased lock is that the lightweight lock will release the lock after use and become a lock-free state
  • When the lock is a biased lock and accessed by another thread, the biased lock will be upgraded to a lightweight lock, and other threads will try to acquire the lock in the form of spin without blocking, thereby improving performance.
  • When the code enters the synchronization block, if the lock state of the synchronization object is a lock-free state (the lock flag is "01", whether it is a biased lock is "0"), the virtual machine will first establish a stack frame in the current thread. The space named Lock Record is used to store a copy of the current Mark Word of the lock object, and then copy the Mark Word in the object header to the Lock Record.
  • After the copy is successful, the virtual machine will use the CAS operation to try to update the Mark Word of the object to a pointer to the Lock Record, and point the owner pointer in the Lock Record to the Mark Word of the object.
  • If the update operation is successful, then the thread owns the lock of the object, and the lock flag of the object Mark Word is set to "00", indicating that the object is in a lightweight lock state.
  • If the update operation of the lightweight lock fails, the virtual machine will first check whether the Mark Word of the object points to the stack frame of the current thread. If it is, it means that the current thread already owns the lock of the object, then it can directly enter the synchronization block to continue. Execute, otherwise it means that multiple threads compete for the lock.
  • If there is currently only one waiting thread, the thread waits by spinning. But when the spin exceeds a certain number of times, or one thread is holding the lock, one is spinning, and there is a third visit, the lightweight lock is upgraded to a heavyweight lock.
  • Multiple threads request the same lock at different times, which means there is no lock competition. In response to this situation, the Java virtual machine uses lightweight locks to avoid blocking and waking up of heavyweight locks
  • The condition for a lightweight lock is that a contention occurs or a lightweight lock has to be acquired. Let's take a look at a case where a lightweight lock has to be applied. Note that the VM attribute is turned on with a biased lock delay and the VM does not do any configuration.
 public class SimpleTest {
     public static void main(String[] args) {
         SimpleTest test = new SimpleTest();
         System.out.println(ClassLayout.parseInstance(test).toPrintable());
         synchronized (test) {
             System.out.println(ClassLayout.parseInstance(test).toPrintable());
         }
     }
 }
复制代码
  • This code is the same as the code above for the biased lock demo anonymous biased lock, the difference is that the configuration of the VM is canceled. That is, the bias lock delay is turned on. Then the markword in the test object we printed for the first time is a lock-free state. It stands to reason that the second time should be on the bias lock. But let's imagine that the delay bias may also be biased when the bias lock is applied for the second time. Isn't this a resource contention? In order to avoid conflicts with the delay bias, the second time is directly lightweight. Lock.

image-20211215091052543.png

Subsequent iterations introduced heavyweight locks.

Guess you like

Origin juejin.im/post/7079947851920310309