Please indicate the original source, thank you!

HappyFeet blog

Recently, I am busy looking for a job, looking for a house, and I have a lot of things to do. Besides, I have changed my city, so I don’t have much mind to write a blog. Now that the job is set and the house is rented, it’s time to adjust your mindset and start blogging.

To be honest, I have met many companies and communicated with many interviewers during this period of time, and felt a lot. However, I have not decided how to write yet. I will organize my experience of resignation and interview into a blog as soon as possible, and please be patient!

Leaving the interview aside, let’s talk about the underlying principles of locksupport.park () and locksupport.unpark ().

Why are we talking about these two methods? The reason is that when reading AQS source code found that the number of calls to these two methods is very much! Locksupport.park () and locksupport.unpark () are the basic implementations of locksupport.unpark ().


Locksupport.park () and locksupport.unpark ()

1. The basics you should know

Park translates to “park” in Chinese and in code means “suspend” the current thread.

In fact, the LockSupport class provides a number of methods, commonly used are the following:

  • Park () : suspend the current thread indefinitely
  • ParkNanos (Long Nanos) : Suspends the current thread for a period of time
  • ParkUntil (long deadline) : Suspends the current thread until the deadline
  • Unpark (Thread Thread) : wakes up the Thread

Unpark is used to wake up the thread, and the other three are used to suspend the thread.

Suspended threads are divided into indefinite and finite suspended threads, with the corresponding states being WAITING and TIMED_WAITING.

A thread suspended indefinitely can be resumed in one of three ways:

  • Another thread calls the unpark method with the suspended thread as the argument

  • Another thread interrupts the suspended thread

  • The call spuriously (that is, for no reason) returns.

    Here is a false wake up, you can refer to the following information:

    • Why does pthread_cond_wait have spurious wakeups?
    • Do spurious wakeups in Java actually happen?

    Due to the existence of false wake up, spin is generally used when calling park, and the pseudocode is as follows:

        while(! canProceed()) { ... LockSupport.park(this);
        }
    Copy the code

In addition to the above three, there are four ways to hang a deadline:

  • A pending period has passed or a specified deadline has been reached

2. What are their characteristics?

This class associates, with each thread that uses it, a permit.
Copy the code

As stated in the Java documentation, each thread is associated with a permit.

The call to Park returns immediately when permission is available, or it may be suspended (false wake up will not be suspended); Calling unpark will make the license available if the license is not available, and it will have no effect if the license itself is available.

It may not be obvious to look at the text directly, but let’s look at a few examples:

  • Example 1: Park suspends the thread and unpark wakes up the suspended thread
public static void exampleOne(a) {
    Thread thread = new Thread(() -> {
        while (flag) {

        }

        System.out.println("before first park");
        LockSupport.park();
        System.out.println("after first park");
        LockSupport.park();
        System.out.println("after second park");

    });

    thread.start();

    flag = false;

    sleep(20);

    System.out.println("before unpark.");
    LockSupport.unpark(thread);
}
Copy the code

The output

before first park
before unpark
after first park
Copy the code

Analysis of the

First, licenses are not initially available; Therefore, the thread is suspended after calling park, and the main thread invokes unpark to wake up the suspended thread. After first park, the thread continues to be suspended after calling park.

  • Example 2: The unpark effect does not accumulate
private static void exampleTwo(a) {
    Thread thread = new Thread(() -> {
        while (flag) {

        }

        System.out.println("before first park");
        LockSupport.park();
        System.out.println("after first park");
        LockSupport.park();
        System.out.println("after second park");

    });

    thread.start();

    LockSupport.unpark(thread);
    LockSupport.unpark(thread);

    flag = false;
}
Copy the code

The output

before first park
after first park
Copy the code

Analysis of the

The main thread unpark the thread twice and then calls the park method twice. The second park will suspend the thread. The main point here is that the unpark effect does not accumulate, and when permissions are available, calling the unpark method has no effect.

  • Example 3: Impact of interruption on Park
private static void exampleThree(a) {
    Thread thread = new Thread(() -> {

        System.out.println("before first park");
        LockSupport.park();
        System.out.println("after first park");
        LockSupport.park();
        System.out.println("after second park");
        System.out.println("isInterrupted is " + Thread.interrupted());
        System.out.println("isInterrupted is " + Thread.interrupted());
        LockSupport.park();
        System.out.println("after third park");
    });

    thread.start();

    sleep(200);

    thread.interrupt();
}
Copy the code

The output

before first park
after first park
after second park
isInterrupted is true
isInterrupted is false
Copy the code

Analysis of the

After thread.interrupted (), thread calls Park three times, but after thread.interrupted () twice. From the output, it is found that park did not take effect for the first two times, and park suspended the thread only for the third time. Why?

Let’s see what thread.interrupted () does: ** checks the interrupted status of the current Thread and clears the interrupted status. You only need to call thread.interrupted () once, but twice to see if the status of the Thread has changed.

When the thread’s interrupt status is true, Park loses the effect and does not suspend the thread. Park returns after thread.interrupted () clears the interrupted status.

So here’s the conclusion: thread interruption invalidates park.

Complete sample code portal

Second, source tracking

Here comes the big one! There are a few ways to park and unpark, Park (Boolean isAbsolute, long time) and unpark(Object thread), both native methods UNSAFE.

Let’s take a look at how these two native methods are implemented.

UNSAFE. Park (Boolean isAbsolute, long time)

(1)unsafe.cpp#Unsafe_Park:openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
  UnsafeWrapper("Unsafe_Park");
  EventThreadPark event;
#ifndef USDT2
  HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
#else /* USDT2 */
   HOTSPOT_THREAD_PARK_BEGIN(
                             (uintptr_t) thread->parker(), (int) isAbsolute, time);
#endif /* USDT2 */
  JavaThreadParkedState jtps(thread, time ! =0); thread->parker()->park(isAbsolute ! =0, time);
#ifndef USDT2
  HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
#else /* USDT2 */
  HOTSPOT_THREAD_PARK_END(
                          (uintptr_t) thread->parker());
#endif /* USDT2 */. UNSAFE_ENDCopy the code

Click to view source code

There are a couple of branch judgments here, but you can see that whichever branch you’re going to end up using the park(bool isAbsolute, jlong time) method.

(2)os_linux.cpp#Parker::park:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void Parker::park(bool isAbsolute, jlong time) {
			...
  if (Atomic::xchg(0, &_counter) > 0) return;

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread"); JavaThread *jt = (JavaThread *)thread; .if (Thread::is_interrupted(thread, false)) {
    return;
  }

  // Next, demultiplex/decode time arguments
  timespec absTime;
  if (time < 0 || (isAbsolute && time == 0)) {// don't wait at all
    return;
  }
  if (time > 0) { unpackTime(&absTime, isAbsolute, time); }...if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) ! =0) {
    return;
  }
  
  int status ;
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    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.
    OrderAccess::fence();
    return;
  }

  assert(_cur_index == - 1."invariant");
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    if(status ! =0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL: os::Linux::condAttr()); }}... _counter =0 ;
  status = pthread_mutex_unlock(_mutex) ;
  OrderAccess::fence();

  // If externally suspended while waiting, re-suspend
  if(jt->handle_special_suspend_equivalent_condition()) { jt->java_suspend_self(); }}Copy the code

Click to view source code

  • Atomic:: XCHG (0, &_counter) = 0; if _counter > 0, the original license is available.

  • If the current thread is interrupted, return directly;

Thread::is_interrupted(Thread, false) checks whether the Thread has interrupted(Thread, false) and does not reset its interrupted state. If thread.interrupted () calls Thread::is_interrupted(Thread, true), the status of the Thread is interrupted and reset.

  • If time < 0 or isAbsolute is true and time = 0, the thread does not need to be suspended and returns directly. Otherwise, convert time to absTime, which will be used in the subsequent waiting with a deadline.
  • Again check the value of _counter. If it is greater than 0, the original license is available, set it to 0, and return directly. There should be an unpark operation between the Atomic:: XCHG and the lock acquisition, making _counter 1;
  • If time == 0, call pthread_cond_wait to wait indefinitely. Otherwise, the OS ::Linux:: safe_cond_timedWait method is called, which ends up calling pthread_cond_timedwait and enters a finite wait.

Pthread_cond_wait can be found in this article: Pthread_cond_wait details

Senses are similar to object. wait, object. notify, and Object.notifyAll.

2, the UNSAFE. Unpark (Object thread)

(1)unsafe.cpp#Unsafe_Unpark:openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
  UnsafeWrapper("Unsafe_Unpark");
  Parker* p = NULL;
  if(jthread ! =NULL) {
    oop java_thread = JNIHandles::resolve_non_null(jthread);
    if(java_thread ! =NULL) {
      jlong lp = java_lang_Thread::park_event(java_thread);
      if(lp ! =0) {
        // This cast is OK even though the jlong might have been read
        // non-atomically on 32bit systems, since there, one word will
        // always be zero anyway and the value set is always the same
        p = (Parker*)addr_from_java(lp);
      } else {
        // Grab lock if apparently null or using older version of library
        MutexLocker mu(Threads_lock);
        java_thread = JNIHandles::resolve_non_null(jthread);
        if(java_thread ! =NULL) {
          JavaThread* thr = java_lang_Thread::thread(java_thread);
          if(thr ! =NULL) {
            p = thr->parker();
            if(p ! =NULL) { // Bind to Java thread for next time.
              java_lang_Thread::set_park_event(java_thread, addr_to_java(p));
            }
          }
        }
      }
    }
  }
  if(p ! =NULL) {
#ifndef USDT2
    HS_DTRACE_PROBE1(hotspot, thread__unpark, p);
#else /* USDT2 */
    HOTSPOT_THREAD_UNPARK(
                          (uintptr_t) p);
#endif /* USDT2 */
    p->unpark();
  }
UNSAFE_END
Copy the code

Click to view source code

The previous chunk of code assigned Parker* p and ended up calling p’s unpark method: p->unpark().

(2)os_linux.cpp#Parker::unpark:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void Parker::unpark() {
  int s, status ;
  status = pthread_mutex_lock(_mutex);
  assert (status == 0."invariant"); s = _counter; _counter =1;
  if (s < 1) {
    // thread might be parked
    if(_cur_index ! =- 1) {
      // thread is definitely parked
      if (WorkAroundNPTLTimedWaitHang) {
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0."invariant");
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0."invariant");
      } else {
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0."invariant");
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0."invariant"); }}else {
      pthread_mutex_unlock(_mutex);
      assert (status == 0."invariant"); }}else {
    pthread_mutex_unlock(_mutex);
    assert (status == 0."invariant"); }}Copy the code

Click to view source code

The unpark method is quite simple. First, the lock is acquired by pthread_mutex_lock, then _counter is set to 1, and if there are any threads currently suspended, the suspended thread is woken up by pthread_cond_signal, and the lock is released.

Three, endnotes

Learning knowledge is really a ring ring, learn a knowledge point, it is easy to encounter new knowledge points, and then continue to contact new knowledge points, constantly learning new content; When you continue to learn, continue to understand the new knowledge, slowly find that many knowledge points are the same idea, learning is not so hard.

For example, when you look at the pthread_cond_wait mechanism, you think of Object.wait because there are many similarities between the two implementations and it is relatively easy to understand.

What is the difference between locksupport. park and Object.wait?

References:

(1) In-depth parsing of interrupt() and locksupport.park () caused by a few small examples

(2) Class LockSupport