About the JDK source code involved in the article, here is the latest JDK source code to share with you —–> JDK source

preface

In the article is the locking mechanism of concurrent Java programming of AQS (AbstractQueuedSynchronizer) we understand the internal structure of the entire AQS, in its exclusive and Shared access to the realization of the synchronization state. However, it does not describe in detail how threads block and wake up. I also mentioned that these operations of the thread are related to the LockSupport utility class. Now let’s explore the implementation of this class.

LockSupport class

To understand thread blocking and waking up, we need to look at the LockSupport class. The specific code is as follows:

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.

    private static void setBlocker(Thread t, Object arg) {
        U.putObject(t, PARKBLOCKER, arg);
    }
    
    public static void unpark(Thread thread) {
        if(thread ! = null) U.unpark(thread); } public static void park(Object blocker) { Thread t = Thread.currentThread();setBlocker(t, blocker);
        U.park(false, 0L);
        setBlocker(t, null);
    }

    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            U.park(false, nanos);
            setBlocker(t, null);
        }
    }

    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        U.park(true, deadline);
        setBlocker(t, null);
    }

 
    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return U.getObjectVolatile(t, PARKBLOCKER);
    }

    public static void park() {
        U.park(false, 0L);
    }

    public static void parkNanos(long nanos) {
        if (nanos > 0)
            U.park(false, nanos);
    }

    public static void parkUntil(long deadline) {
        U.park(true, deadline); } / / omit part of the code private static final sun. Misc. Unsafe U = sun. Misc. Unsafe. GetUnsafe (); private static final long PARKBLOCKER; private static final long SECONDARY; static { try { PARKBLOCKER = U.objectFieldOffset (Thread.class.getDeclaredField("parkBlocker"));
            SECONDARY = U.objectFieldOffset
                (Thread.class.getDeclaredField("threadLocalRandomSecondarySeed")); } catch (ReflectiveOperationException e) { throw new Error(e); }}}Copy the code

From the above code, we can see that the methods provided externally in LockSupport are static methods. These methods provide the most basic thread-blocking and wake up functionality. In the LockSupport class, a set of methods starting with Park are defined to block the current thread. And unPark(Thread Thread) to wake up a blocked Thread. The specific description of the method to start with Park is as follows:

Among them, park(Object Blocker) and parkNanos(Object Blocker, long Nanos) and parkUntil(Object Blocker, long Deadline) are three new methods added in Java 6. The blocker parameter is used to identify the object on which the thread is waiting. This object is mainly used for troubleshooting and system monitoring.

Because prior to Java 5, when a thread was blocking (using the synchronized keyword) on an object, it was possible to see the blocked object of that thread through thread dump. This is missing from concurrency tools such as Java 5 exit Lock, making it impossible to provide information about blocked objects when a thread dumps. Therefore, in Java 6, LockSupport added a park method that contains blocking objects. To replace the original Park method.

The blocker LockSupport

If you’re wondering how Blocker works, how do you get blocked objects into a thread? Without further ado, let’s move on to the Park method that contains an Object Blocker. We noticed that the setBlocker(Thread T, Object arg) method was called internally. The specific code is as follows:

   private static void setBlocker(Thread t, Object arg) {
        U.putObject(t, PARKBLOCKER, arg);
    }
Copy the code

U is the Unsafe class under the sun.misc. Package. The PARKBLOCKER is assigned in a static block of code like this:

private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
  static {
        try {
            PARKBLOCKER = U.objectFieldOffset
                (Thread.class.getDeclaredField("parkBlocker")); / / omit part of the code} the catch (ReflectiveOperationException e) {throw new Error (e); }}Copy the code

Thread. Class. GetDeclaredField (” parkBlocker “) method is easy to understand, actually is parkBlocker field in the Thread. If so, the corresponding Field is returned. If not, NoSuchFieldException is thrown. What about the objectFieldOffset(Field f) method in Unsafe?

Before describing this method, there is a general point to make. Within the JVM, you are free to choose how to implement the “layout” of Java objects, meaning that the JVM can perceive and determine where parts of a Java object are placed in memory. In Sun.misc. Unsafe, the objectFieldOffset() method is provided to retrieve a field relative The offset of the “starting address” of a Java object, which also provides methods like getInt, getLong, and getObject to access a field of a Java object using the offset previously obtained.

It may be difficult for you to understand, so here is a diagram to help you understand, as shown below:

In the figure above, we created two Thread objects, Thread 1 has an address 0x10000-0x10100 in memory, and Thread 2 has an address 0x11000-0x11100 in memory, where parkBlocker corresponds to an offset of 2 (we assume that the offset from its object’s “starting position” is 2) ). The offset of this Field can be obtained by objectFieldOffset(Field f). Note that the memory offset of a field is always the same in its class, that is, the parkBlocker field is always the same at the memory offset of its object for Thread object 1 and Thread object 2.

Back to the setBlocker(Thread t, Object arg) method, u.Putobject (t, parkBlocker, arg) is called when we get the parkBlocker field’s Object memory offset. , the method takes three parameters, the first parameter is the operation object, the second parameter is the memory offset, and the third parameter is the actual stored value. This method is also simple to understand. It simply manipulates data at a memory address in an object. So combine that with what we said above. The actual operation result of this method is shown in the figure below:

By now, we should know that even though the current thread is blocked, we can directly manipulate the memory area in the thread where the field is actually stored to achieve the desired result.

LockSupport underlying code implementation

Reading the source code, we can see that LockSupport blocks and wakes up threads, Ununsafe calls the park(Boolean isAbsolute, long time) and unpark(Object Thread) methods in sun.misc.Unsafe:

    private static final jdk.internal.misc.Unsafe theInternalUnsafe =   
      jdk.internal.misc.Unsafe.getUnsafe();
      
	public void park(boolean isAbsolute, long time) {
        theInternalUnsafe.park(isAbsolute, time);
    }
    public void unpark(Object thread) {
        theInternalUnsafe.unpark(thread);
    }
Copy the code

Looking at the Unsafe. Java file in the sun.misc. Package, we can see that the underlying call is the method in JDK.internal.misc. Looking further at the corresponding method in Unsafe. Java in JDK.internal.misc. :

    @HotSpotIntrinsicCandidate
    public native void unpark(Object thread);

    @HotSpotIntrinsicCandidate
    public native void park(boolean isAbsolute, long time);
Copy the code

By looking at the methods, we can see that the final call is from the JVM, which is the one from the hotspot.share.parims package, unsafe.cpp. Keep tracking.

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, Thread ->parker()->park(isAbsolute! = 0, time); } UNSAFE_END UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread)) { Parker* p = NULL; // Omit some codeif(p ! = NULL) { HOTSPOT_THREAD_UNPARK((uintptr_t) p); p->unpark(); } } UNSAFE_ENDCopy the code

By looking at the code, we can see that thread blocking and awakening are actually related to the Parker class in hotspot. Share. Runtime. Let’s continue:

class Parker : public os::PlatformParker { private: volatile int _counter ; // This variable is very important and will be described in detail belowParker() { ShouldNotReachHere(); } public: // For simplicity of interface with Java, all forms of park (indefinite, // relative, and absolute) are multiplexed into one call. void park(bool isAbsolute, jlong time); void unpark(); // omit some code}Copy the code

In the code above, the value of volatile int _counter is important. Be careful that it is volatile (described below), and that when using the SourceInsight tool (we recommend reading the code), Using the tool) click the park and unpark methods and we get the following screen:

As you can see from the red rectangle, different operating systems have different implementations for blocking and waking up threads. Java is known to be cross-platform. For different platforms, make different processing. It’s very understandable. This is because the author is not familiar with Windows and Solaris operating systems. So HERE I choose to analyze the platform under Linux. The os_POSIx. CPP file in the hotspot.os.posix package is selected for analysis.

Implementation of Park under Linux

In order to make it easier for you to understand the blocking implementation under Linux, I have omitted some unimportant code in the actual code, as shown below:

Void Parker::park(bool isAbsolute, jlong time) {//(1) Return _counter if the value is greater than 0if (Atomic::xchg(0, &_counter) > 0) return; Thread* Thread = Thread::current(); JavaThread *jt = (JavaThread *)thread; //(2) If the current thread has been interrupted, return directly.if (Thread::is_interrupted(thread, false)) {
    return; Struct timespec absTime if the time is less than 0, or in absolute case, the time is 0;if (time < 0 || (isAbsolute && time == 0)) { // don't wait at all return; } // If the time is greater than 0, judge the blocking timeout or blocking expiration date and assign the time to absTime if (time > 0) {to_absTime (&absTime, time, isAbsolute); } / / (4) if the current Thread has been interrupted, or apply for a mutex, failure is returned directly if (Thread: : is_interrupted (Thread, false) | | pthread_mutex_trylock (_mutex)! = 0) { return; If (time == 0) {_cur_index = REL_INDEX; // arbitrary choice when not timed status = pthread_cond_wait(&_cond[_cur_index], _mutex); assert_status(status == 0, status, "cond_timedwait"); } //(6) Block thread duration based on the time calculated before absTime else {_cur_index = isAbsolute? ABS_INDEX : REL_INDEX; status = pthread_cond_timedwait(&_cond[_cur_index], _mutex, &absTime); assert_status(status == 0 || status == ETIMEDOUT, status, "cond_timedwait"); } //(0, 0, 0, 0, 0); status = pthread_mutex_unlock(_mutex); // omit some code}Copy the code

From the whole code, there are actually seven steps about the Park method under Linux:

  • (1) CallAtomic::xchgMethods,_counterIs assigned to 0, and the return value of its method isThe previous value of _counter, if return valueGreater than zeroThe value of _counter was called by another thread because it was manipulated by another threadunParkMethod), then return directly.
  • (2) If the current thread has been interrupted, return directly. That is, calling the park() method to block the thread will not work if the current thread is already interrupted.
  • (3) Judge whether the set time is reasonable. If so, judgeBlocking timeoutorBlocking deadline, and assign the time toabsTime
  • (4) Before actually blocking the thread, judge again if the current thread has been interrupted or failed to apply for the mutex, and return directly
  • (5) If time = 0 (time = 0, means that the thread is blocked until unPark is called), then the thread is blocked directly.
  • (6) According to the time calculated before absTime, and callpthread_cond_timedwaitMethod blocks the thread for the corresponding time.
  • (7) When the thread blocks for the corresponding time, throughpthread_mutex_unlockMethod directly wakes up the thread, while the_counterAssign a value of 0.

Because blocking on Linux involves its internal functions, the functions used here are declared. You can understand it according to the following table. Specific methods are shown in the following table:

Linux under unpark implementation

Now that you know the Park implementation of Linux, it’s easy to understand the wake up implementation of Linux:

void Parker::unpark() {
  int status = pthread_mutex_lock(_mutex);
  assert_status(status == 0, status, "invariant"); const int s = _counter; // set _counter to 1 _counter = 1; // must capture correct index before unlocking int index = _cur_index; status = pthread_mutex_unlock(_mutex); assert_status(status == 0, status,"invariant"); // omit some code}Copy the code

Pthread_mutex_unlock (_mutex) is the final method to wake up a thread in the code as a whole. See the diagram in the Park implementation for Linux. If the value of _counter is set to 1, and the park method is used, we can see that the whole thread is aroused and blocked by the value of _counter in the Parker class on Linux.

The use of LockSupport

Now we have a basic understanding of LockSupport. Now let’s look at its basic use. In this example, I called the Park method with Blocker in it to make it easier for you to see what blocker does. The specific code is as follows:

class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.park("Thread A blocker data");
                System.out.println("I'm waking up on thread B."); }}); a.start(); Thread.sleep(1000); // Let Thread A sleep for 1 second before Thread B. Thread b = new Thread(newRunnable() {
            @Override
            public void run() {
                
                String before = (String) LockSupport.getBlocker(a);
                System.out.println("Get blocker from thread A when blocking ------>"+ before); LockSupport.unpark(a); Try {thread.sleep (1000); String after = (String) LockSupport.getBlocker(a); System.out.println("Get blocker from thread A on wake up ------>"+ after); } catch (InterruptedException e) { e.printStackTrace(); }}}); b.start(); }}Copy the code

In this code, two threads are created, thread A and thread B (thread A runs first with thread B). In thread A, call locksupport. park(” Thread A’s Blocker data “); Thread A is given a blocker of type String to block thread A while it is running. In thread B, the blocker from thread A is first fetched and printed. Through LockSupport. Unpark (a); Wake up thread A. When thread A wakes up. Finally, the Blocker in thread A is printed and printed. The actual code run result is as follows:

Blocker ------>null from thread A when thread B wakes upCopy the code

From the result, we can see that when thread A is blocked, there will be no further operations. When thread A is woken up by thread B. The blocker that was set earlier is now null. And if there are additional operations after the park statement in thread A. Then it will continue to run. Before the blocker becomes null for the following reasons:

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        U.park(false, 0L); // When a thread is blocked, it will block heresetBlocker(t, null); // When the thread wakes up, it sets blocer to null}Copy the code

From the example above, we know that Blocker can retrieve data if the thread blocks. It turns out that Blocker is very useful when it comes to thread troubleshooting and system monitoring.

The last

This article refers to the following blog, standing on the shoulders of giants. You can see further.

Linux multithreading – Thread asynchrony and synchronization mechanisms

LockSupport analysis and use

Write your own “lock” –LockSupport is simple