Make writing a habit together! This is the third day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

This paper introduces the LockSupport blocking tool in JUC and the basic principle of park and unpark methods, from Java level to JVM level.

1 Overview of LockSupport

public class LockSupport extends Object

LockSupport, derived from JDK1.5 and located in the LOCKS subpackage of the JUC package, is a handy thread blocking utility class that defines a set of common static methods that provide the most basic thread blocking and wake up functionality and can be blocked and woken up anywhere within a thread.

In the AQS framework source code, when the need to block or wake up a thread, will use the LockSupport tool to complete. LockSupport and CAS are the basis for implementing concurrent tools (locks and other synchronization classes) in Java. These, in turn, depend on the Unsafe class. Unsafe only maintains a series of native method interfaces, so the real implementation is in HotSpot’s source code. HotSpot is implemented in C++!

This article first explains the general principle of LockSupport and Java code implementation, and finally introduces Hotspot low-level implementation. The AQS framework is the cornerstone for implementing synchronous components in JUC, and LockSupport can be said to be one of the cornerstones of the AQS framework.

2 Features and principles of LockSupport

2.1 features

  1. LockSupport is non-reentrant, which is easy because park simply means to block a thread, not “lock”, and the thread is blocked once the park method is called.
  2. LockSupport’s park blocking and unpark wake-up calls do not require any condition objects, nor do they need to acquire any locks first. To decouple the code to some extent, LockSupport is only tied to threads, and the threads being parked do not release previously acquired locks.
  3. Park blocking and unpark wake up can be called in reverse order, without deadlocks, and unpark can be called multiple times; If the stop and resume methods are in reverse order, a deadlock occurs.
  4. Park supports interrupt wake up but does not throw InterruptedException, which can be obtained from the isInterrupted does not clear the interrupt flag), interrupted (clears the interrupt flag) methods.

2.2 the principle

Each thread is associated with a permit. The unpark function provides permit to the thread, which calls the park function and waits and consumes permit.

The default value of permit is 0. If you call unpark once, it will become 1. If you call park once, you will consume permit.

If there is no permit, the call to park blocks the relevant thread and waits for a permit at the call point. In this case, the call to unpark sets permit to 1, causing the blocked thread to wake up.

Each thread has its own permit, but permits hold at most one, and repeated calls to unpark do not accumulate.

Compared to Thread.suspend and Thread.resume, locksupport. park and locksupport. unpark do not cause deadlocks (if resume is executed before suspend, threads cannot continue). Because of permission, even if unpark occurs before park, it can cause the next park operation to return immediately.

Unlike Object.wait, lockSupport. park does not require an Object lock first and does not throw InterruptedException.

In contrast to synchronized, a thread blocked by locksupport.park () can be blocked by interrupt, but does not throw an exception, and the interrupt flag bit is not cleared after the interrupt.

The thread blocked by park is in WAITING, and the thread blocked by park is in TIMED_WAITING.

The above is very simple to understand the principle, there will be a detailed explanation!

3 LockSupport method analysis and testing

3.1 Basic Methods

LockSupport defines a set of methods starting with park to block the current thread and an unpark method to wake up a blocked thread.

/** * try to obtain a permission, if not, block the current thread, response interrupt; Call unpark(Thread Thread) to obtain permission. The unpark operation can be performed before or after park, or if park has previously obtained permission, the unpark call returns * 2. The current thread is interrupted () and returns without throwing an exception * 3. Return due to false wake up */
public static void park(a) {
    UNSAFE.park(false.0L);
}

/** * extension function of park(). Time is a period relative to the current time, in nanoseconds, and automatically returns ** if timeout occurs@paramNanos time range nanosecond */
public static void parkNanos(long nanos) {
    if (nanos > 0)
        UNSAFE.park(false, nanos);
}

/** * extension function of park(), the time is based on the point in absolute time (since 1970) in milliseconds, and automatically returns ** if timeout occurs@paramDeadline Indicates the time point in milliseconds */
public static void parkUntil(long deadline) {
    UNSAFE.park(true, deadline);
}

/** * provides a permission to wake up the thread. * 1. If the thread has no previous permission, the number of threads will not increase by * 2. If the thread was previously suspended by calling park(), it will be woken up after calling unpark(). * 3. If the thread did not call park() before, the unpark() method will return immediately after the next call to park(). * *@param thread
 */
public static void unpark(Thread thread) {
    if(thread ! =null)
        UNSAFE.unpark(thread);
}
Copy the code

3.2 New method of JDK1.6

Prior to JDK1.5, when a thread blocked on a monitor object with the synchronized keyword, the blocked object of the thread could be viewed through the dump to facilitate problem location. However, when JDK1.5 introduced LockSupport, this was omitted. Because the LockSupport method does not require a monitor object or a lock to execute, information about blocked objects cannot be provided when looking at thread dumps.

Therefore, in JDK1.6, LockSupport has added three park methods with blocking objects and a method to obtain the broker instead of the park method to facilitate problem location.

/** * the new method in JDK1.6 is the same as park() except for arguments: SetBlocker () specifies the blocker object that the thread is waiting for. The blocker is used by thread monitoring and analysis tools to locate the blocker object. Clear the record again when the thread lock is released; * It is recommended to use this method rather than park() because this function records the initiator of the block, which is easy to check if a deadlock occurs and is explicitly seen in thread dumps * *@paramBlocker Is the blocking object */ associated with the thread
public static void park(Object blocker) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Record which object initiated the blocking operation on the thread
    setBlocker(t, blocker);
    // Suspend the thread
    UNSAFE.park(false.0L);
    // The thread is awakened and the broker is cleared
    setBlocker(t, null);
}


/** * The park(Object Blocker) option increases the timeout duration by nanoseconds@paramBlocker Is the blocking object * associated with the thread@paramNanos timeout period */
public static void parkNanos(Object blocker, long nanos) {
    if (nanos > 0) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, nanos);
        setBlocker(t, null); }}/** * The same as park(Object Blocker), adds a timeout point in milliseconds@paramBlocker Is the blocking object * associated with the thread@paramDeadline Expiration time */
public static void parkUntil(Object blocker, long deadline)


/** * View the blocking object associated with the thread. If you don't set Blocker, you can't get the thread
public static Object getBlocker(Thread t) {
    if (t == null)
        throw new NullPointerException();
    return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}


/** * Sets the broker method, which belongs to LockSupport's private method **@paramT Current thread *@paramThe ARG sets the broker object */
private static void setBlocker(java.lang.Thread t, Object arg) {
    // The UNSAFE method is also called internally
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

/** * In the Thread definition, there is a parkBlocker property, which holds the broker property */
public class Thread implements Runnable {
    volatile Object parkBlocker;
    / /...
}
Copy the code

3.3 test

3.3.1 Park/Unpark basic test

/** * unpark test */
@Test
public void test2(a) {
    System.out.println("begin park");
    // Call the park method
    LockSupport.park();
    // Make the current thread obtain the license, obviously cannot perform this step, because it was blocked in the previous step
    LockSupport.unpark(Thread.currentThread());
    System.out.println("end park");
}

/** * unpark test */
@Test
public void test3(a) {
    System.out.println("begin park");
    // Make the current program get the license first
    LockSupport.unpark(Thread.currentThread());
    // The park method is called again with permission first, so it does not block
    LockSupport.park();
    System.out.println("end park");
}

/** * unpark test */
@Test
public void test4(a) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run(a) {
            long currentTimeMillis = System.currentTimeMillis();
            System.out.println("begin park");
            LockSupport.park();
            System.out.println("end park"); System.out.println(System.currentTimeMillis() - currentTimeMillis); }}); thread.start();// Open or comment this line of code to observe the end park time
    //Thread.sleep(2000);
    // Make the child thread get the license
    LockSupport.unpark(thread);
}
Copy the code

3.3.2 Park Thread status test

/** * park thread state test **@throws InterruptedException
 */
@Test
public void test1(a) throws InterruptedException {
    / / park unlimited
    Thread thread = new Thread(() -> LockSupport.park());
    / / park time limit
    Thread thread2 = new Thread(() -> LockSupport.parkNanos(3000000000l));
    thread.start();
    thread2.start();
    // The main thread sleeps for a second to allow the child threads to run fully
    Thread.sleep(1000);
    // Get the state of the child thread in park
    System.out.println(thread.getState()); 
    System.out.println(thread2.getState());
}
Copy the code

As a result,

WAITING TIMED_WAITING

3.3.3 Park interrupts test

/** * park interrupts test */
@Test
public void test5(a) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run(a) {
            // The initial interrupt flag bit is false
            System.out.println(Thread.currentThread().isInterrupted());
            long currentTimeMillis = System.currentTimeMillis();
            System.out.println("begin park");
            LockSupport.park();
            System.out.println("end park");
            System.out.println(System.currentTimeMillis() - currentTimeMillis);
            // After calling the interrupt method, the interrupt flag bit is trueSystem.out.println(Thread.currentThread().isInterrupted()); }}); thread.start();// Open or comment this line of code to observe the end park time
    Thread.sleep(2000);
    // Blocking caused by park can also be interrupted with interrupt, but this interrupt does not throw an exception
    thread.interrupt();
}
Copy the code

3.3.4 Park Broker test

/** * Park Broker test */
public static void main(String[] args) {
    Run CMD, run JPS, find the pid corresponding to the process, and then run jstack pid to see the thread information.
    //LockSupport.park();
    LockSupport.park(new LockSupportTest());
}
Copy the code

Comment out one of the methods separately and get the following result (find the main thread) :

Using Park, boroker information cannot be seen:

Using park(Broker), you can see broker information, so it is recommended to block threads using this method:

4 Basic implementation principles of LockSupport

4.1 the Unsafe

In LockSupport’s rationale, we say that “each thread is associated with a permit.” This sentence, if not in-depth, then there is no problem, and the underlying implementation is indeed related to this “permit”, but not quite accurate.

If you look in the Thread implementation class for the permit property or permit related property, you will be disappointed to find that there is no permit property. Where is the Thread associated with the permit property?

Above we studied the LockSupport method and the source code, but you’ll find it “unnervingly simple”, and you’ll notice that all types of park and unpark methods ultimately refer to the broadening method:

/** * The method in Unsafe frees a thread that was blocked by park. It can also be used to terminate a block caused by a previous call to park, i.e. both methods can be called unpark before park. * *@paramThread thread * /
public native void unpark(Object thread);

/** * The method in Unsafe blocks the current thread until an unpark method is present (called), an unpark method has been present (called before the park method is called), the thread is interrupted or time expires (that is, blocking timeout), or a false wake up. * In the case of non-zero time, if isAbsolute is true, time is a millisecond relative to a new epoch (1970), otherwise time indicates when the current nanosecond time period is * *@paramIsAbsolute indicates whether the time isAbsolute. True: yes false: no *@paramTime Indicates the millisecond value if it is the absolute time; otherwise, it indicates the nanosecond period relative to the current time */
public native void park(boolean isAbsolute, long time);
Copy the code

You can see that both of these methods are native methods, i.e. “native methods” or JNI, indicating that this method allows Java to interact directly with other native languages such as C and C++.

The Unsafe class has many native methods, such as field offsets (similar to Pointers to C), that allow the Java language to interact with underlying systems directly. It has support for direct memory management, thread blocking & wake up, CAS operations, direct manipulation of classes, objects, variables, etc.

The broadening native method is implemented by Hotspot, so we have to look at Hotspot’s source code. The Oracle JDK doesn’t provide Hotspot’s source code, so we have to look it up in Openjdk. The Unsafe implementation can be found in Openjdk8.

Now we are in the world of C++. The following code involves Hotspot source code and C++ syntax, if you feel that it does seem a bit difficult then please watch carefully, for ordinary people, understand the principle of LockSupport is enough.

First give Openjdk8 kind of Unsafe Java implementation: (hg.openjdk.java.net/jdk8u/jdk8u…

/ / park
UNSAFE_ENTRY(void.Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
  / /.....................
  // Call Parker's park method
  thread->parker() - >park(isAbsolute ! =0, time);
  / /.....................
UNSAFE_END


UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
  / /.....................
  // Call Parker's unpark method
    p->unpark(a);
  / /.....................
UNSAFE_END

Copy the code

We can see that Parker’s park and unpark methods will eventually be called.

4.2 the Thread

The first thing we should understand is that when we create a call to thread.start, what the underlying system does is that the start method actually ends up calling the JNI method as well. This will create an instance of a C++ implementation JavaThread. JavaThread creates an OSThread instance through the POSIX interface create_Thread. Osthreads represent native threads in OS. Thread instances, JavaThread instances, and OSThread instances have a one-to-one relationship. After the OSThread is created, the OSThread executes JavaThread’s run method, which executes Thread’s run method.

The first is the Hotspot in all kinds of Thread to achieve a common Thread (hg.openjdk.java.net/jdk8/jdk8/h…

class Thread: public ThreadShadow {
 protected:
  // OS data associated with the thread
  OSThread* _osthread;  // Platform-specific thread information
 / /.....................
 public:  
  ParkEvent * _ParkEvent ;                     // for synchronized()
  ParkEvent * _SleepEvent ;                    // for Thread.sleep
  ParkEvent * _MutexEvent ;                    // for native internal Mutex/Monitor
  ParkEvent * _MuxEvent ;                      // for low-level muxAcquire-muxRelease
 / /.....................
}
Copy the code

There we can find some key field information, such as _osThread, which corresponds to the underlying native OSThread thread, and some ParkEvent properties, which are not useful in this article, but as an extension, ParkEvent actually corresponds to the implementation of Java synchronized keyword at the JVM level, and also implements wait, notify, and sleep functions. Our article on the implementation of synchronized will analyze the source code here in depth. Simply speaking, it is to achieve multi-threaded synchronization (lock). In ObjectWaiter’s implementation, there is also the ParkEvent property.

Then let’s look at the JavaThread implementation, also in thread.hpp:

class JavaThread: public Thread {
 private:
  JavaThread*    _next;                          // The next thread in the Threads list
  oop            _threadObj;                     // The Java level thread object
  // JSR166 per-thread parker
private:
  Parker*    _parker;
public:
  Parker*     parker(a) { return_parker; }};Copy the code

JavaThread has an internal _threadOb property, which essentially holds aThread object at the Java level. JavaThread inherits Thread from the _osThread field. A JavaThread object corresponds to both an OSThread object and aThread object, so the three of them are related. So essentially a Java Thread corresponds to an OS Thread.

The Unsafe can directly operate the JVM and the underlying system, therefore, can be found directly by the Thread is JavaThread instance, so even if we didn’t find it in the Thread “permit”, but the “permit” is certainly in the Hotspot source can meet!

JavaThread also has a _parker property of type Parker. This Parker property is used to implement the park and unpark of LockSupport in Java. That is, JUC’s middle thread blocking, wake up implementation at the JVM level.

4.3 Parker

In the source file (hg.openjdk.java.net/jdk8/jdk8/h Thread…

// ======= JavaThread ========

// A JavaThread is a normal Java thread

void JavaThread::initialize(a) {
  // Initialize fields
  / /.....................
    // Call Parker's Allocate method to pass the current JavaThread thread
    _parker = Parker::Allocate(this); }Copy the code

Take a look at Parker (hg.openjdk.java.net/jdk8/jdk8/h…

class Parker : public os::PlatformParker {
private:
  // Count, actually this is called "permit permit"
  volatile int _counter ;
  // Next Parker
  Parker * FreeNext ;
  // The thread associated with Parker
  JavaThread * AssociatedWith ;

public:
  Parker() : PlatformParker() {
  // Initialize permission to 0
    _counter       = 0 ;
    FreeNext       = NULL ;
    AssociatedWith = NULL ;
  }
protected:
  ~Parker() { ShouldNotReachHere(a); }public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  // Park and unpark actually end up calling Parker's method of the same name
  void park(bool isAbsolute, jlong time);
  void unpark(a);

  // Lifecycle operators
  // Accept a thread and return a new Parker. This is how Parker is initialized in JavaThread init
  static Parker * Allocate (JavaThread * t) ;
  static void Release (Parker * e) ;
private:
  static Parker * volatile FreeList ;
  static volatile int ListLock ;

};
Copy the code

Parker has a _counter field, which is essentially what we call “permission” and is initialized to 0 by default. The park and unpark methods we call are actually Parker’s namesake methods.

Here we finally find the true realization of the oft-said “license”! Let’s look at the underlying principles of Park and unpark!

4.4 PlatformParker

The Hotspot virtual machine is cross-platform and has different implementations for different operating systems. The most common one is Linux. Let’s take a look at PlatformParker under Linux (hg.openjdk.java.net/jdk8/jdk8/h…

class PlatformParker : public CHeapObj<mtInternal> {
  protected:
    enum {
        REL_INDEX = 0,
        ABS_INDEX = 1
    };
    // The subscript index of the array of conditional variables
    //-1 indicates the initialization value, that is, no condition variable is currently in use
    //0 represents the first condition variable in the array, which is used to suspend the thread at park relative time
    //1 represents the second condition variable in the array, which is used to suspend the thread at park absolute time
    int _cur_index;  // which cond is in use: -1, 0, 1
    // Mutex's underlying thread synchronization tool: mutex
    pthread_mutex_t _mutex [1];//condition Low-level thread synchronization tool: condition variable. There are two of them, one is relative time and the other is absolute time
    pthread_cond_t  _cond  [2];// one for relative times and one for abs.

  public:       // TODO-FIXME: make dtor private
    ~PlatformParker() { guarantee (0."invariant"); }public:
    PlatformParker() {
      int status;
      // initialize _mutex and _cond
      status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr());
      assert_status(status == 0, status, "cond_init rel");
      status = pthread_cond_init (&_cond[ABS_INDEX], NULL);
      assert_status(status == 0, status, "cond_init abs");
      status = pthread_mutex_init (_mutex, NULL);
      assert_status(status == 0, status, "mutex_init");
      // where _cur_index is initialized to -1
      _cur_index = - 1; // mark as unused}};Copy the code

PlatformParker has the POSIX library standard mutex and condition, so Parker’s implementation of park and unpark is actually implemented using these two tools.

PlatformParker also has a _cur_index property, which has a value of -1, 0, or 1. The value is initialized at -1 and is also set to -1 by the thread that calls park and returns. If it is not -1, then a thread is suspended on the condition variable in the corresponding PARKER. _cur_index equals 0 means that the thread calling the relative time of Park is suspended on the first condition variable, and 1 means that the thread calling the absolute time of Park is suspended on the second condition variable.

4.5 Overview of MUtex and Condition

As mentioned above, mutex and condition are both posiX-standard tools for implementing thread synchronization on the underlying system threads. Mutex is called a mutex lock. Similar to Java locks, mutex locks are used to keep threads safe. Only one thread can acquire mutex at a time. The condition, which is analogous to the Java condition, is called a condition variable. It is used to suspend a thread that does not meet the condition on the specified condition variable, and wake up the corresponding thread when the condition is met.

The Condition operation itself is not thread-safe. It has no locking function and can only make the thread wait or wake up. Therefore, mutex and Condition are often used together, which can be analogous to Lock and Condition in Java, or synchronized and monitor objects. Generally, after a thread obtains a mutex lock, the thread is asked to suspend the mutex lock on one Condition and release the mutex lock if the Condition is not met. When another thread obtains the mutex lock and finds that a Condition is met, The method calling Conditon can wake up the thread waiting on the specified Conditon and acquire the lock. Then the awakened thread can execute safely and in accordance with the business rules because the condition is met and the lock is obtained.

The implementation of mutex and condition actually uses queues internally, which can be analogous to the synchronous queue and conditional queue of AQS in Java. Similarly, a thread awakened in the condition queue will be placed in a synchronous queue to acquire the MUtex lock, and will return the mutex lock only after it has acquired the mutex lock. This is similar to the implementation logic of AQS ‘await and signal.

As you can see, the implementation of the AQS framework in JUC actually borrows mutex and condition from the underlying system. Once we understand the implementation of AQS, it is easy to understand the relationship between MUtex and condition. The difference is that AQS is implemented in the Java language, while mutex and condition are system tools implemented in C++. AQS threads block park and wake up unpark using mutex and condition method calls. AQS:JUC – AbstractQueuedSynchronizer (AQS), fifty thousand – word source depth resolution and application cases.

The source code implementation of mutex and condition is not covered here, it will be covered in a later article!

4.6 park method

Next we can look at the implementation of park and unpark. In the Hotspot VIRTUAL machine, there is no unified implementation of these two methods, but different operating systems have their own implementations. Generally we use Linux system, so let’s take a look at the Linux system (hg.openjdk.java.net/jdk8/jdk8/h here…

Let’s first look at the implementation of Park in Linux system, and the steps are as follows:

  1. First check to see if the permission _counter is greater than 0. If so, unpark has been executed before, then reset _counter to 0 and return directly, there is no and no need to fetch mutex.
  2. If the current thread is interrupted, return directly.
  3. If the time value is less than 0, or if it is absolute time and the time value is equal to 0, then it is also returned.
  4. If the current thread is interrupted, it returns the mutex lock. Otherwise, it acquires the mutex lock non-blocking. If it does not acquire the mutex lock, another thread may have acquired the mutex lock in the unpark thread, and returns the mutex lock.
  5. Once the lock is acquired, check again if _counter is greater than 0, if so, then you already have permission, set _counter to 0, release the mutex lock, and return.
  6. Set the value of _cur_index (0 or 1) and call pthread_cond_WAIT or safe_cond_timedwait to enter the corresponding conditional variable wait and automatically release the MUtex lock. The subsequent code does not execute at this point.
  7. When we wake up, we do not actively acquire the mutex lock, because the kernel automatically acquires the mutex lock for us, and resets _counter to 0, indicating that licenses have been expended. Reset _cur_index to -1 to indicate that no threads are waiting. The park method ends.
/* isAbsolute specifies whether the time isAbsolute. Time indicates the milliseconds since GMT, otherwise indicates the nanosecond relative to the current time */
void Parker::park(bool isAbsolute, jlong time) {
  // Ideally we'd do something useful while spinning, such
  // as calling unpackTime().

  // Optional fast-path check:
  // Return immediately if a permit is available.
  // We depend on Atomic::xchg() having full barrier semantics
  // since we are doing a lock-free update to _counter.
  If _counter is greater than 0, set _counter to 0; otherwise, _counter is 0
  if (Atomic::xchg(0, &_counter) > 0) return;
  // Get the current Thread Thread
  Thread* thread = Thread::current(a);assert(thread->is_Java_thread(), "Must be JavaThread");
  // Force the thread to JavaThread
  JavaThread *jt = (JavaThread *)thread;

  // Optional optimization -- avoid state transitions if there's an interrupt pending.
  // Check interrupt before trying to wait
  // If the current thread has an interrupt flag set, the park method returns directly
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

  // Next, demultiplex/decode time arguments
  timespec absTime;
  If the time value is less than 0, or if the absolute time value is 0, then the time value is also returned
  if (time < 0 || (isAbsolute && time == 0)) {// don't wait at all
    return;
  }
  // If the time value is greater than 0, then calculate the timing time (according to isAbsolute set time precision).
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }


  // Enter safepoint region
  // Beware of deadlocks such as 6317397.
  // The per-thread Parker:: mutex is a classic leaf-lock.
  // In particular a thread must never block on the Threads_lock while
  // holding the Parker:: mutex. If safepoints are pending both the
  // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
  // Construct a ThreadBlockInVM object and enter the safe point where the thread blocks
  ThreadBlockInVM tbivm(jt);

  // Don't wait if cannot get lock since interference arises from
  // unblocking. Also. check interrupt before trying wait
  // If the current thread is interrupted, return directly
  // Or pthread_mutex_trylock fails to acquire the mutex lock (returns 0, any other return value is an error), such as if a thread has already called unpark and acquired the mutex
  // Note that pthread_mutex_trylock does not block if it fails to get, but immediately returns a non-zero value
  if (Thread::is_interrupted(thread, false) | |pthread_mutex_trylock(_mutex) ! =0) {
    return;
  }
  // It indicates that the mutex is successfully obtained. Only then can the mutex be unlocked
  int status ;
  // If _counter is greater than 0, there is permission and no need to wait
  if (_counter > 0)  { // no wait needed
    / / _counter set to 0
    _counter = 0;
    // This step releases the mutex (unlocks) and returns
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0."invariant");// Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other and Java-level accesses.
      // This is actually a StoreLoad memory barrier instruction that ensures visibility, and volatile writes also use this barrier
    OrderAccess::fence(a);return;
  }

#ifdef ASSERT
  // Don't catch signals while blocked; let the running threads have the signals.
  // (This allows a debugger to break into the running thread.)
  sigset_t oldsigs;
  sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals(a);pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif
  // Set the operating system thread to CONDVAR_WAIT, not object.wait (), which is the operating system thread state
  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent(a);// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  assert(_cur_index == - 1."invariant");
  // If the time is 0, it means relative time, then suspend the thread
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    A mutex is required to prevent multiple threads from requesting pthread_cond_wait at the same time
    // Release the _mutex lock as well
    // Pthread_cond_wait is not called in the while loop, which may result in a false wake up
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  }
  /* Otherwise, the time is not 0*/
  else {
    // Use different parameters to determine whether time is relative or absolute
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    // call safe_cond_timedwait (pthread_cond_timedwait); If the condition is not met before a given time, ETIMEDOUT is returned to end the wait
    // Release the _mutex lock as well
    // There is no safe_cond_timedwait in the while loop, which may result in a false wake up
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    // If the suspension fails
    if(status ! =0 && WorkAroundNPTLTimedWaitHang) {
    // Clear the condition variable
      pthread_cond_destroy (&_cond[_cur_index]) ;
      // reinitialize the condition variable
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr()); }}/* The following is the logic after being awakened */
  _cur_index = - 1;
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");

#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
  //_counter can be reset to 0
  _counter = 0 ;
  // Release mutex (lock)
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant");// Paranoia to ensure our locked and lock-free paths interact
  // correctly with each other and Java-level accesses.
  // This is actually a StoreLoad memory barrier instruction that ensures visibility, and volatile writes also use this barrier
  OrderAccess::fence(a);// If externally suspended while waiting, re-suspend
  // If stop or suspend is called while the thread is suspended by park, the call to javA_suspend_self will continue the thread suspension
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}
Copy the code

Hotspot source code for the implementation of park method, the thread is suspended and woken up using POSIX standard mutex and condition tools, first need to obtain mutex mutex lock, after the condition variable suspended operation, finally release mutex mutex lock.

We can also see that the “permission” is actually the _counter property in the Parker class. When there is a permission: _counter>0, park can return and must consume the permission at the end of the method: set _counter to 0.

In addition, if the thread calling park does not return, that is, it is blocked on some condition variable, then _cur_index (this property is equal to one on PlatformParker) will not equal -1; After the thread returns, _cur_index is set to -1 again at the end of the park method.

4.6.1 Spurious Wakeup

If there are multiple threads using the same _counter, then the calls to the suspended methods pthread_cond_wait and safe_cond_timedwait must be wrapped in a while loop, which, after being woken up, determines whether the condition is really met. Otherwise, it is possible to wake up at the same time that other threads consume the condition and do not meet it. In this case, a “false wake up” occurs, that is, the blocked thread is woken up, but the condition is not actually met, so it needs to continue waiting. For example, it is correct to write:

while(_counter==0){
    status = pthread_cond_wait(a); }Copy the code

However, in the park method, pthread_cond_wait and safe_cond_timedwait methods are called only once, and there is no loop wrapping. This is because there is one Parker instance for each thread. Different threads have different Parker instances. _counter in each Parker only records the permission count of the threads currently bound, although Parker can still be contended by multiple threads (because other threads need to control the wake up of the Parker bound thread through the unpark method), However, the pthread_cond_wait and safe_cond_timedwait methods (also known as park methods) do not have the possibility of multiple threads competing to call, because the threads calling park are waiting themselves, so there is no need to use the while loop. If a thread is woken up, it’s usually another thread that called the unpark method for that thread, and the permission is usually sufficient, so there’s really nothing wrong with not using the while loop. However, some extreme cases can still result in a “spurious wakeup”, in which the park method can still be returned even if the license is insufficient.

The reason why a “false wake” can occur when the park method only calls a thread to wait is that in Linux, a wait can return even if there is no signal or signalAll call from a thread in the Condition queue. Because thread blocking is usually done using some low-level tool, such as a Futex component, if the underlying component process is interrupted, it terminates the thread blocking and returns an EINTR error status. This is also the third reason for the return written in the Park method:

But that’s rarely the case, and it’s just stating that it’s possible.

4.7 unpark method

The unpark method is simpler than the park method. It is also implemented in the os_linux.cpp file.

  1. The mutex lock is first acquired blocking, and is blocked until it is acquired.
  2. After acquiring the mutex lock, get the value of the current license _counter and store it in the variable S, then set the value of _counter to 1.
  3. If s is less than 1, it indicates that there is no permission. At this time, threads may or may not be suspended. Continue to judge:
    1. If _cur_index is not -1, then there must be a condition variable on the index corresponding to _cur_index that is suspended and needs to be awakened: If set WorkAroundNPTLTimedWaitHang (Linux the default Settings), then the signal first awakened threads that are waiting on the condition variable and release the mutex lock, method over; Otherwise, the mutex lock is released and signal wakes up the thread waiting on the condition variable.
    2. Otherwise, _cur_index equals -1, indicating that there are no threads waiting on the condition variable. The mutex lock is released and the method ends.
  4. Otherwise, s is equal to 1, that means there’s always permission, so you do nothing, just unlock releases the mutex lock, end of method.
/* Provide a license */
void Parker::unpark(a) {
  int s, status ;
  If the mutex has been acquired, the thread will block at this method until the mutex has been acquired
  status = pthread_mutex_lock(_mutex);
  assert (status == 0."invariant");// Save the old _counter
  s = _counter;
  // By setting _counter to 1, you can also see that no matter how many times unpark is called, the "permissions" don't get more
  _counter = 1;
  // If _counter is 0, there is no permission, and threads may or may not be suspended
  if (s < 1) {
    // If _cur_index is not equal to the initial value -1, then a thread is suspended on the corresponding condition variable of parker
    //_cur_index is 0 because the park method is suspended on the first condition variable,
    //_cur_index is 1 because the park method is called in absolute time and suspended on the second condition variable,
    if(_cur_index ! =- 1) {
      // thread is definitely parked
      / * if set WorkAroundNPTLTimedWaitHang, then call signal first, then call unlock, otherwise the opposite * /
      / / WorkAroundNPTLTimedWaitHang is a JVM parameter, the default is 1
      if (WorkAroundNPTLTimedWaitHang) {
        Signal wakes up a thread waiting on the specified condition variable
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0."invariant");
        // Unlock releases the mutex
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0."invariant");
      }
      /* Unlock signal*/
      else {
        // Unlock releases the mutex
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0."invariant");
        Signal wakes up a thread waiting on the specified condition variable
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0."invariant"); }}/* Otherwise, there are no threads waiting on the condition variable. Simply unlock releases the mutex, because the park method returns with _cur_index set to -1*/
    else {
      pthread_mutex_unlock(_mutex);
      assert (status == 0."invariant"); }}/* Otherwise, that means the original _counter is 1, that means there is always a license, so simply unlock releases the mutex */
   else {
    pthread_mutex_unlock(_mutex);
    assert (status == 0."invariant"); }}Copy the code

5 LockSupport summary

LockSupport is a blocking tool provided in JDK1.5 to implement a single thread wait and wake mechanism. It is also a cornerstone of the AQS framework. The other two are CAS operations and the volatile keyword.

The underlying principles of CAS and volatile in Java, which have been explained in previous sections, are LockSupport, This is the underlying implementation of the PARK blocking and unpark wake up mechanism in JUC (note that this is different from the wait() blocking and notify() wake up mechanisms of synchronized). With CAS, LockSupport, and volatile, we can use the Java language to implement locking, or AQS in JUC.

The LockSupport and CAS methods, on the other hand, invoke JNI methods of the Unsafe class, which are ultimately implemented by virtual machines such as Hotspot, and the volatile keyword is marked with special access flags at compile-time, and the JVM handles bytecode execution accordingly. In fact, the blocking, wake up, synchronization, sleep and other low-level mechanisms of Java threads are implemented at the JVM level, but this is not the end. In JVM, it is common to call some POSIX system functions (such as mutex, Condition and other tools and methods, which are provided by the operating system). Ultimately, it goes down to the operating system level, where the Java level mostly provides callable interfaces and some simple logic.

Using LockSupport. Park does not release synchronized or lock locks, because LockSupport has nothing to do with what we call “locks”. You can block a thread by calling LockSupport anywhere, with or without a lock. It is only associated with a single thread, so relying on LockSupport alone does not allow you to “lock”.

LockSupport’s park and unpark methods depend on mutex and Condition tools to implement them in the system.

Related articles:

  1. AQS: JUC – AbstractQueuedSynchronizer (AQS), fifty thousand – word source depth resolution and application cases.
  2. Volatile: In-depth analysis and application of volatile in Java.
  3. CAS: Java CAS implementation principle analysis and application.
  4. UNSAFE: JUC — The principles and use cases for the UNSAFE class.

If you don’t understand or need to communicate, you can leave a message. In addition, I hope to like, collect, pay attention to, I will continue to update a variety of Java learning blog!