当前位置:网站首页>Face to face manual · Chapter 15 code peasant association lock, synchronized detoxification, analysis source code depth analysis! "

Face to face manual · Chapter 15 code peasant association lock, synchronized detoxification, analysis source code depth analysis! "

2020-11-06 01:17:31 Bugstack wormhole stack

author : Little brother Fu
Blog :https://bugstack.cn

precipitation 、 Share 、 grow up , Let yourself and others have something to gain !

One 、 Preface

It feels like nothing , Where to start !

It's a question I've always been asked lately , It's true . A new programmer , Or an old driver who wants to study hard again , It won't either , That won't be , Always worry about where to start .

Be reasonable , After all Java There is too much knowledge involved , Learning should be the ability to learn , Instead of memorizing the topic 、 Recite the answer , There will not be much profit from picking up people's wisdom .

The process of learning should find the right way , When you have a problem, you'd better think about it yourself , How do you learn this knowledge . Do not feel even if you go to Baidu search , You don't even know which keyword to search for ! Can only take the question to ask people directly , This lack of thinking , Lack of brain bumping into the south wall , In fact, it's hard to learn .

therefore , What you need to learn is the ability to learn from yourself , After that, you can start from anywhere , It's important to start and stick to !

Two 、 Interview questions

Thank you plane , Notes , Weekend shopping at outlets , I went back to the interviewer's house !

Thank you plane :duang、duang、duang, I'm coming !

interviewer : I came on time , Wash your hands and eat !

Thank you plane : Hey …

interviewer : Look at my fish tofu , Does it look similar? synchronized lock !

Thank you plane : ah !?

interviewer : The plane , Just to ask you .synchronized、volatile, What's the difference ?

Thank you plane : Um. ,volatile Guaranteed visibility ,synchronized Guaranteed atomicity !

interviewer : That's not necessary volatile, Only synchronized The way to decorate , Can you guarantee visibility ?

Thank you plane : this …, I haven't verified !

interviewer : Eat it. , Eat it. ! I'll give you a synchronized Syllabus , Sort out the knowledge points according to !

3、 ... and 、synchronized detoxification

 chart  15-0  The interviewer gave Xie an ,synchronized  Syllabus

1. Object structure

1.1 Introduction to object structure

 chart  15-1 64 position JVM Object structure description

HotSpot virtual machine markOop.cpp Medium C++ Code comment fragment , It describes 64bits Next mark-word Storage status of , Is the figure 15-1 The structural sketch of .

This part of the source code comments are as follows :

64 bits:
--------
unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
size:64 ----------------------------------------------------->| (CMS free block)

unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

Source code address jdk8/hotspot/file/vm/oops/markOop.hpp

HotSpot In the virtual machine , The layout of objects stored in memory can be divided into three areas : Object head (Header) The instance data (Instance Data) and Alignment filling (Padding).

  • mark-word: Object tag fields occupy 4 Bytes , The tag bit used to store some columns , such as : Hash value 、 Tag bit of lightweight lock , Bias lock mark bit 、 Generational age, etc .
  • Klass Pointer:Class Object type pointer ,Jdk1.8 By default, when the pointer is compressed, it is 4 byte , Turn off pointer compression (-XX:-UseCompressedOops) after , The length is 8 byte . It points to the position corresponding to the object Class object ( Its corresponding metadata object ) Memory address of .
  • Object actual data : Include all member variables of the object , The size is determined by each member variable , such as :byte Occupy 1 Bytes 8 bits 、int Occupy 4 Bytes 32 bits .
  • alignment : In the end, it's not necessary to complete this space , Just to be a place holder . because HotSpot The memory management system of virtual machine requires that the starting address of the object must be 8 Integer multiples of bytes , So the head of the object happens to be 8 Multiple of bytes . So when the data part of the object instance is not aligned , It needs to be filled by aligning the padding .

in addition , stay mark-word In the lock type tag , unlocked , Biased locking , Lightweight lock , Weight lock , as well as GC Mark ,5 You can't use it in the category 2 Bit tags (2 Bits eventually have 4 Combinations of 00011011), So there's no lock 、 Biased locking , The front occupies a bias lock mark . Final :101 For no lock 、001 For biased locks .

1.2 Verify object structure

In order to see the object structure more intuitively , We can use openjdk Provided jol-core Print analysis .

introduce POM

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-cli</artifactId>
    <version>0.14</version>
</dependency>

Test code

public static void main(String[] args) {
    System.out.println(VM.current().details());
    Object obj = new Object();
    System.out.println(obj + "  Hexadecimal hash :" + Integer.toHexString(obj.hashCode()));
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
1.2.1 Pointer compression on ( Default )

Running results

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 chart  15-2  Pointer compression on , Object head layout

  • Object object , Take up a total of 16 byte
  • The head of the object occupies 12 Bytes , among :mark-down Occupy 8 byte 、Klass Point Occupy 4 byte
  • Last 4 byte , It is used for data filling and alignment
1.2.2 Pointer compression off

stay Run-->Edit Configurations->VM Options Configuration parameters -XX:-UseCompressedOops Turn off pointer compression .

Running results

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 12 0c 53 (00000001 00010010 00001100 01010011) (1393299969)
      4     4        (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
      8     4        (object header)                           00 1c b9 1b (00000000 00011100 10111001 00011011) (465116160)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

 chart  15-3  Pointer compression off , Object head layout

  • After turning off pointer compression ,mark-word Or occupy 8 The byte doesn't change .
  • Focus on type pointers Klass Point The change of , From the original 4 byte , Now it's expanded to 8 byte .
1.2.3 Object header hash value storage verification

Next , Let's adjust the test code , Take a look at how hash values are stored in the object header .

Test code

public static void main(String[] args) {
    System.out.println(VM.current().details());
    Object obj = new Object();
    System.out.println(obj + "  Hexadecimal hash :" + Integer.toHexString(obj.hashCode()));
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
  • Not many changes , Just print the hash value and the object , It is convenient for us to verify the storage result of the object header about the hash value .

Running results

 chart  15-3  The hash value of the object header is stored

  • Pictured 15-3, The hash value of the object is 16 It's binary ,0x2530c12
  • Look at the result of storing the hash value of the object header , There are also corresponding values . It's just that the result is reversed .

The reverse question about this is because , Large and small end storage results in ;

  • Big-Endian: The high byte is stored in the low address side of memory , The low order byte is stored in the high address end of memory
  • Little-Endian: The low order byte is stored in the low address side of memory , The high bits are stored in the high address end of memory

mark-down structure

 chart  15-5  No lock state ,64 Bit virtual machine mark-down structure

Pictured 15-5 The most the right side 3 Bit(1 Bit Identification bias lock ,2 Bit Describe the type of lock ) It's the type of lock and GC Mark relevant , and synchronized The lock optimization upgrade expansion is to modify the identification of these three bits , To distinguish between different lock types . In order to take different strategies to improve performance .

1.3 Monitor object

stay HotSpot In the virtual machine ,monitor By C++ in ObjectMonitor Realization .

synchronized Operation mechanism of , Is that when JVM Monitoring objects in different competitive situations , Will automatically switch to the appropriate lock implementation , This switch is the upgrade of lock 、 Downgrade .

So three different Monitor Realization , That is to say, there are three different locks : Deflection lock (Biased Locking)、 Lightweight lock and heavyweight lock . When one Monitor After being held by a thread , It's locked .

Monitor The main data structure is as follows

// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;       //  Record the number 
    _waiters      = 0,
    _recursions   = 0;       //  Number of thread reentries 
    _object       = NULL;    //  Storage  Monitor  object 
    _owner        = NULL;    //  Holding the current thread  owner
    _WaitSet      = NULL;    //  be in wait Thread in state , Will be added to  _WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;   //  One way list 
    FreeNext      = NULL ;
    _EntryList    = NULL ;   //  Waiting lock block Thread in state , Will be added to the list 
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
}

Source code address jdk8/hotspot/file/vm/runtime/objectMonitor.hpp

  • ObjectMonitor, There are two queues :_WaitSet _EntryList, For preservation ObjectWaiter The object list .
  • _owner, obtain Monitor Object's thread enters _owner Regional time , _count + 1. If the thread calls wait() Method , This will release Monitor object , _owner Return to empty , _count - 1. At the same time, it's time to wait for the thread to enter _WaitSet in , Waiting to be awakened .

The lock execution effect is as follows

 chart  15-06, Lock execution effect

Pictured 15-06, Every Java The object header contains Monitor object ( The point of the stored pointer ),synchronized In other words, the lock is obtained in this way , Which explains why synchronized() Any object in parentheses can get a lock !

2. synchronized characteristic

2.1 Atomicity

Atomicity An operation is uninterruptible , Either all execution succeeds or all execution fails .

Case code

private static volatile int counter = 0;
public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() -> {
            for (int i1 = 0; i1 < 10000; i1++) {
                add();
            }
        });
        thread.start();
    }
    //  etc. 10 Finished running threads 
    Thread.sleep(1000);
    System.out.println(counter);
}
public static void add() {
    counter++;
}

This code opens 10 Threads to add up to counter, According to the expected result, it should be 100000. But the actual operation will find that ,counter The value is less than 10000, This is because volatile There is no guarantee of atomicity , So the end result will not be 10000.

Modification method add(), add to synchronized:

public static void add() {
    synchronized (AtomicityTest.class) {
        counter++;
    }
}

This time the test result is :100000 了 !

because synchronized It can guarantee that only one thread can get the lock at the same time , Go to code block execution .

Decompile view script

javap -v -p AtomicityTest

public static void add();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: ldc           #12                 // class org/itstack/interview/AtomicityTest
         2: dup
         3: astore_0
         4: monitorenter
         5: getstatic     #10                 // Field counter:I
         8: iconst_1
         9: iadd
        10: putstatic     #10                 // Field counter:I
        13: aload_0
        14: monitorexit
        15: goto          23
        18: astore_1
        19: aload_0
        20: monitorexit
        21: aload_1
        22: athrow
        23: return
      Exception table:

Synchronization method

ACC_SYNCHRONIZED This is a sync flag , Corresponding 16 The base value is 0x0020

this 10 When threads enter this method , Will judge whether there is such a logo , And then it started to compete Monitor object .

Synchronization code

  • monitorenter, In the judgment of having a synchronization identifier ACC_SYNCHRONIZED The thread that enters this method first has priority Monitor Of owner , Now the counter +1.
  • monitorexit, When the execution is finished, exit , Counter -1, return 0 After that, it is obtained by other threads .

2.2 visibility

In the previous chapter volatile In the article , We know that it guarantees the visibility of variables to all threads . The end result is to add volatile When the attribute variable of , Threads A After changing the value , Threads B Use this variable to make a corresponding response , such as while(! Variable ) sign out .

that ,synchronized Is there visibility , Let's give an example .

public static boolean sign = false;
public static void main(String[] args) {
    Thread Thread01 = new Thread(() -> {
        int i = 0;
        while (!sign) {
            i++;
            add(i);
        }
    });
    Thread Thread02 = new Thread(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException ignore) {
        }
        sign = true;
        logger.info("vt.sign = true  while (!sign)")
    });
    Thread01.start();
    Thread02.start();
}

public static int add(int i) {
    return i + 1;
}

This is an example of two threads manipulating a variable , Because the variables between threads sign The invisibility of , Threads Thread01 Medium while (!sign) Will always carry out , Not with threads Thread02 modify sign = true And out of the loop .

Now? We give the method add add to synchronized Keyword modification , as follows :

public static synchronized int add(int i) {
    return i + 1;
}

Add and run the result

23:55:33.849 [Thread-1] INFO  org.itstack.interview.VisibilityTest - vt.sign = true  while (!sign)

Process finished with exit code 0

You can see when the thread Thread02 Change variables sign = true after , Threads Thread01 Immediately out of the loop .

Be careful : Do not add System.out.println() , Because this method contains synchronized It will affect the test results !

So why add synchronized It can also guarantee the visibility of variables ?

because :

  1. Before thread unlocking , The latest value of the shared variable must be refreshed to main memory .
  2. Before thread locking , The value of shared variables in working memory will be cleared , Thus, when using shared variables, you need to re read the latest value from main memory .
  3. volatile The visibility of is all through the memory barrier (Memnory Barrier) To achieve .
  4. synchronized By operating system kernel mutex lock , amount to JMM Medium lock、unlock. Refresh variables to main memory when exiting code block .

2.3 Orderliness

as-if-serial, Ensure that no matter how the compiler and processor reorder instructions for performance optimization , We need to ensure the correctness of the running results under single thread . That's what they say : If you observe in this thread , All operations are orderly ; If you look at one thread at another , All operations are out of order .

There's a double check lock here (Double-checked Locking) Classic case :

public class Singleton {
    private Singleton() {
    }

    private volatile static Singleton instance;

    public Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

Why? ,synchronized There's also a visibility feature , It also needs to be volatile keyword ?

because ,synchronized The order of , No volatile To prevent instruction reordering .

Well, if you don't add volatile Keywords may result in , The first thread is initializing the initialization object , Set up instance When pointing to a memory address . When the second thread enters , There's an order to rearrange . In judging if (instance == null) There is a possibility of making mistakes , Because it could be instance It may not have been initialized successfully .

2.4 Reentrancy

synchronized It's a reentrant lock , in other words , Allow a thread to request the critical resource that holds the object lock twice , This is called a reentrant lock .

So let's write an example , To prove that .

public class ReentryTest extends A{

    public static void main(String[] args) {
        ReentryTest reentry = new ReentryTest();
        reentry.doA();
    }

    public synchronized void doA() {
        System.out.println(" Subclass method :ReentryTest.doA() ThreadId:" + Thread.currentThread().getId());
        doB();
    }

    private synchronized void doB() {
        super.doA();
        System.out.println(" Subclass method :ReentryTest.doB() ThreadId:" + Thread.currentThread().getId());
    }

}


class A {
    public synchronized void doA() {
        System.out.println(" Parent class method :A.doA() ThreadId:" + Thread.currentThread().getId());
    }
}

test result

 Subclass method ReentryTest.doA() ThreadId1
 Parent class method A.doA() ThreadId1
 Subclass method ReentryTest.doB() ThreadId1

Process finished with exit code 0

This singleton code is a recursive call that contains synchronized The way to lock , From the test results that run normally , There was no deadlock . All can prove that synchronized It's a reentrant lock .

synchronized There's a counter for locking objects , He will record the number of times a thread acquires a lock , After executing the corresponding code block , The counter will -1, Until the counter is cleared , And the lock was released .

The reason , You can re-enter . Because synchronized The lock object has a counter , After the thread acquires the lock +1 Count , When the thread is finished executing -1, Until the lock is cleared and released .

3. Lock upgrade process

About synchronized Lock upgrade has a very complete picture , You can refer to :

 chart  15-7 synchronized  Lock upgrade process

synchronized Locks have four alternate escalation States : unlocked 、 Biased locking 、 Lightweight locks and heavyweight , These states escalate with competition .

3.1 Biased locking

synchronizer Source code :/src/share/vm/runtime/synchronizer.cpp

// NOTE: must use heavy weight monitor to handle jni monitor exit
void ObjectSynchronizer::jni_exit(oop obj, Thread* THREAD) {
  TEVENT (jni_exit) ;
  if (UseBiasedLocking) {
    Handle h_obj(THREAD, obj);
    BiasedLocking::revoke_and_rebias(h_obj, false, THREAD);
    obj = h_obj();
  }
  assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");

  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj);
  // If this thread has locked the object, exit the monitor.  Note:  can't use
  // monitor->check(CHECK); must exit even if an exception is pending.
  if (monitor->check(THREAD)) {
     monitor->exit(true, THREAD);
  }
}
  • UseBiasedLocking It's a biased lock check ,1.6 After that, it is turned on by default ,1.5 China is closed , It needs to be opened manually. The parameters are XX:-UseBiasedLocking=false

The deflection lock will delay JIT Warm up process , So many performance tests explicitly turn off the skew lock , Skew lock is not suitable for all application scenarios , Cancel the operation (revoke) It's a heavy act , It's only when there's more that won't really compete synchronized When I was a kid , In order to show obvious improvement .

3.2 Lightweight lock

When the lock is biased towards the lock , Accessed by another thread , Biased locks will be upgraded to lightweight locks , Other threads try to acquire the lock by spinning , It won't block , Improve performance .

When the code enters the synchronized block , If the lock state of the synchronization object is unlocked ( The lock flag bit is “01” state , Whether it is biased lock or not is “0”),JVM The virtual machine starts by creating a record called a lock in the current thread's stack frame (Lock Record) Space , Used to store the current lock object Mark Word A copy of the , Officially called Displaced Mark Word.

3.3 spinlocks

Spin locks are threads that attempt to acquire locks that do not immediately block , It's a loop to try to get the lock , The advantage is to reduce the consumption of thread context switching , The disadvantage is that the cycle consumes CPU.

The default size of the spinlock is 10 Time , You can adjust :-XX:PreBlockSpin

If spin n Failed , Will be upgraded to heavyweight locks . Heavyweight locks , stay 1.3 Monitor Object has already introduced .

3.4 Will locks degrade ?

I always knew that Java Lock demotion will not occur , But recently, after sorting out a lot of data, it is found that lock degradation does happen .

When safepoints are used?

Below are few reasons for HotSpot JVM to initiate a safepoint:
Garbage collection pauses
Code deoptimization
Flushing code cache
Class redefinition (e.g. hot swap or instrumentation)
Biased lock revocation
Various debug operation (e.g. deadlock check or stacktrace dump)

Biased lock revocation, When JVM Enter the safe point SafePoint When , Will check if there are any idle Monitor, And then try to demote .

Four 、 summary

  • This chapter is about synchronized Lock involves more C++ Source code analysis learning , Source code address :https://github.com/JetBrains/jdk8u_hotspot
  • In addition to the details of lock mining mentioned in this article, there are many knowledge points to continue to learn , Can combine ifeve、 Concurrent programming 、 In depth understanding of JVM virtual machine , And so on .
  • Combine in the learning process C++ Source code on the implementation of locks , It's easier to understand concepts that may have been obscure . In combination with the actual case verification , It's easy to accept this part of knowledge .
  • Okay , That's all for this article , If there is an inaccurate expression of opinions and articles, please leave a message , learn from each other , Mutual literacy , Make progress with each other .

5、 ... and 、 Fu Shi's first hand

  • The clubhouse , The code in the farmer's Union lock .
  • crowded , You need to raise the price and upgrade .
  • project 🤯, Massage the scalp .
  • effect 🤨, We can see that atoms are ordered .

6、 ... and 、 Series recommendation

( Please indicate the author and source of this article WeChat official account :bugstack Wormhole stack | author : Little brother Fu

Show Disqus Comments

版权声明
本文为[Bugstack wormhole stack]所创,转载请带上原文链接,感谢