Gnaw through the Java source code Detailed concurrent -LockSupport

Java1.5 added JUC and contracting, like a Swiss Army knife easy to use, greatly enriched the means to handle concurrent Java, but JUC is not simple, there is a certain learning curve, I have also seen some off JUC realize source, but neither system is not enough depth, the decision to re-start, re-master of God as I read Doug Lea, so he is also holding the mentality to learn Leveling, record their own learning experience, it is inevitable that understanding is not in place where light spray of Kazakhstan.

Why Read Source

I do not know you have no such feeling, when processing tools provided in the concurrent use of JUC, there is a feeling rote, such as how LockSupport should use, CountDownLatch can be doing, but its implementation principle is not clear, just know how I do not know why, this state has two large problems.

  • Rote, relatively easy to forget, easy to use at work digging, high risk
  • JUC is not deep enough understanding, knowledge can not form a system, it is difficult to digest, flexible use

To that depth, the most direct and effective way is to read the source code!

Why must first resolve LockSupport

We know JUC appears to have a lot of class, complex structure, but if you want to pick out the most important class, it must be a queue synchronizer AbstractQueuedSynchronizer, and AbstractQueuedSynchronizer is to control the use of LockSupport state of the thread, so as to achieve threads are waiting wake switching between object. And we deal with concurrency, the focus is the management of state of the thread, so it is important to understand LockSupport a foundation.

Simple use of LockSupoort

Let's look at a simple example

    public static void main(String[] args) {

        Thread worker = new Thread(() -> {
            LockSupport.park();
            System.out.println("start work");
        });

        worker.start();

        System.out.println("main thread sleep");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LockSupport.unpark(worker);

        try {
            worker.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    // 最终控制台输出结果
    main thread sleep
    start work
复制代码

Start a worker thread, the main thread to sleep 500ms, worker thread calls because the park LockSupport, it will wait until the end of the main thread sleep, wake up call to unpark worker threads. So before JUC, we used to make thread wait method is as follows

        Object monitor = new Object();
        synchronized (monitor) {
            try {
                monitor.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

复制代码

There are three main differences

  1. LockSupport.park unpark and need not block the synchronization code, wait and notify is needed.
  2. LockSupport of pork and unpark is for the thread, and wait and notify that can be any object.
  3. LockSupport the unpark gives the specified thread wakes up, but notify a random wake, notifyAll is all wake up, is not flexible enough.

LockSupport source Interpretation

The foregoing merely pave the way, and now to our entrees, interpretation LockSupport the park and unpark method, of course, there are other similar overloaded methods, such as parkUntil, parkNanos, which are similar to the general principle of self-interest we can review the source code.

This article and subsequent articles, source code analysis are based on the Open Jdk 8.

LockSupport.unpark

Why talk about unpark method, because less code unpark few, relatively simple, Persimmon first pick soft pinch -. -

    //java.util.concurrent.locks.LockSupport.java
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    
    //
    UNSAFE = sun.misc.Unsafe.getUnsafe();
复制代码

Parameter thread is our goal to awaken the thread, first sentence empty, then call UNSAFE.unpark, UNSAFE is Unsafe objects, do not be scared of this name, this class provides many useful methods, the previous article also mentioned, such as Get the object class attribute memory offset address, and the CAS operation. However, this must be reflected Unsafe objects obtained before normal use, since the method has determined getUnsafe current class loader is not BootStrapClassLoader. We continue to see the implementation of Unsafe class unpark.

// Unsafe.java
public native void unpark(Object thread);
复制代码

You can see unpark a native method, its native implementation is achieved in the hotspot \ src \ share \ vm \ prims \ unsafe.cpp look at the code,

UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
  UnsafeWrapper("Unsafe_Unpark");
  // 声明一个Parker对象p,它是真正干活的对象
  Parker* p = NULL;
  if (jthread != NULL) {
    // 根据传入的jthread对象,来获取native层的oopDesc*对象,oop是oopDesc* 的宏定义
    oop java_thread = JNIHandles::resolve_non_null(jthread);
    if (java_thread != NULL) {
      // 获取java_thread对象中_park_event_offset的值,该值就是Parker对象的地址
      jlong lp = java_lang_Thread::park_event(java_thread);
      if (lp != 0) {
        // 如果地址有效,直接转为Parker指针
        p = (Parker*)addr_from_java(lp);
      } else {
        // 如果地址无效
        MutexLocker mu(Threads_lock);
        java_thread = JNIHandles::resolve_non_null(jthread);
        if (java_thread != NULL) {
          // 转为native层的JavaThread对象 
          JavaThread* thr = java_lang_Thread::thread(java_thread);
          if (thr != NULL) {
            // 将JavaThread的成员变量_parker赋值给p
            p = thr->parker();
            if (p != NULL) { // Bind to Java thread for next time.
              // 将p的地址赋值给_park_event_offset,下次获取时可用
              java_lang_Thread::set_park_event(java_thread, addr_to_java(p));
            }
          }
        }
      }
    }
  }
  if (p != NULL) {
  // 这个USDT2的宏,暂时我也不清楚是干啥的,不过不影响我们的分析,我们先忽略
#ifndef USDT2
    HS_DTRACE_PROBE1(hotspot, thread__unpark, p);
#else /* USDT2 */
    HOTSPOT_THREAD_UNPARK(
                          (uintptr_t) p);
#endif /* USDT2 */
    // 真正干货的方法,调用了Parker的unpark方法
    p->unpark();
  }
复制代码

The above code, we need to know the class two native layer, JavaThread classes and class Parker

class JavaThread: public Thread {
private:
  JavaThread*    _next;                          // The next thread in the Threads list
  oop            _threadObj;                     // The Java level thread object
 // 省略代码...
private:
  Parker*    _parker;
public:
  Parker*     parker() { return _parker; }
  // 省略代码...
复制代码

JavaThread class very long, just to name a few member variables, it is now only need to know Thread native layer, member variables _threadObj is Java thread object layer by layer, it can call native code for Java layer. We continue to focus on to achieve Parker look like.

class Parker : public os::PlatformParker {
private:
  volatile int _counter ;
  // 省略代码...
  }
复制代码

We focus on _counter field, can be simply understood as _counter field> 0, you can access that method will directly return to park, another park after the method returns, _counter will be assigned 0, unpark method can be set to 1 _counter and wake up the thread is currently waiting.

Parker can see the parent class is os :: PlatformParker, then this class is doing it? Here to insert a digression, we all know, Java is cross-platform, we defined at the application layer Thread certainly depends on the specific platform, different platforms have different implementations, such as Linux is a set of code, Windows is another set that we can understand, PlatformParker implemented differently depending on the platform. OpenJdk8 support in the implementation of the five platforms

  • aix
  • bsd
  • linux
  • solaris
  • windows

We know that Linux is now more widely used operating system, such as well-known Android is based on Linux kernel, so here we choose Linux to analyze it. The corresponding file path hotspot \ src \ os \ linux \ vm \ os_linux.cpp

void Parker::unpark() {
  int s, status ;
  // 先进入_mutex的临界区,声明如下
  // pthread_mutex_t _mutex [1] ;
  // pthread_cond_t  _cond  [2] ; 
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  // 将_counter置为1
  _counter = 1;
  // s记录的是unpark之前的_counter数,如果s < 1,说明有可能该线程在等待状态,需要唤醒。
  if (s < 1) {
    // thread might be parked
    // _cur_index代表被使用cond的index
    if (_cur_index != -1) {
      // thread is definitely parked
      // 根据虚拟机参数WorkAroundNPTLTimedWaitHang来做不同的处理,默认该参数是1
      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");
        // 后发信号唤醒线程,唤醒操作在互斥代码块外部,感觉这里可能会有风险,暂时还GET不到。。。
        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") ;
  }
}
复制代码

Code is small, have added a note, in general, is based on the class member variables Park _counter do lock unlock and wake-up operation, the Linux platform, locking with pthread_mutex_lock, unlocking is pthread_mutex_unlock, wake-up pthread_cond_signal. The next analytical method LockSupport the park.

LockSupport.park

Look at the Java layer method to achieve park

    public static void park() {
        UNSAFE.park(false, 0L);
    }
复制代码

The realization Unsafe

public native void park(boolean var1, long var2);
复制代码

Continues to be a native method, we look to continue to follow up

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
 // 省略代码...
  thread->parker()->park(isAbsolute != 0, time);
 // 省略代码...
复制代码

Omitting non-critical code focuses on park method, this thread as we have encountered, the object is JavaThread native layer, and then call the park method Parker and continue with the inside of the linux platform to achieve os_linux.cpp

void Parker::park(bool isAbsolute, jlong time) {
  // 先原子的将_counter的值设为0,并返回_counter的原值,如果原值>0说明有通行证,直接返回
  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;
  // park方法的传参是isAbsolute = false, time = 0,所以会继续往下走
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  // 这里time为0,如果调用的是parkNanos或者parkUtil,这里time就会>0,
  if (time > 0) {
    // 如果time > 0,unpackTime计算absTime的时间
    unpackTime(&absTime, isAbsolute, time);
  }

  ThreadBlockInVM tbivm(jt);

  // 再次判断线程是否被中断,如果没有被中断,尝试获得互斥锁,如果获取失败,直接返回
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

  int status ;
  // 如果_counter > 0, 不需要等待,这里再次检查_counter的值
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    // 插入一个写内存屏障,保证可见性,具体实现见下方
    OrderAccess::fence();
    return;
  }

// 省略assert代码

  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  // 设置JavaThread的_suspend_equivalent为true,表示线程被暂停
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  assert(_cur_index == -1, "invariant");
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    // 让线程等待_cond[_cur_index]信号,到这里线程进入等待状态
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    // 线程进入有超时时间的等待,内部实现调用了pthread_cond_timedwait系统调用
    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());
    }
  }
  _cur_index = -1;
// 省略assert代码
 // _counter重新设置为0
  _counter = 0 ;
  // 释放互斥锁
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // 插入写屏障
  OrderAccess::fence();

  // 省略额外检查代码
}

// OrderAccess::fence 在linux平台的实现
inline void OrderAccess::fence() {
  // 如果是多核cpu
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
    // 判断是否AMD64 CPU,汇编代码实现写屏障
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}
复制代码

Analysis of the implementation of the above principles park, knowledge bedding, park in front unpark method with the method of analysis should be easy to understand.

Harvesting and summary

LockSupport by reading the source code, can be summed up the following points

  1. JavaThread native layer is represented by _counter Parker's pass,> 0 indicates passable, if _counter = 0, call park thread will wait until it is unpark wake up, if first call unpack, then call the park directly return and consumption off _counter (set to 0).
  2. Linux platform, and wake-up thread waiting, locked with pthread_mutex_lock, unlocking is pthread_mutex_unlock, wake-up pthread_cond_signal, we learned that there are a few of these hearts, know these know why, How Yoshiya!

Finally, still would like to mention Java layer little knowledge about the state of the thread, there may be some students will not particularly clear, so it is still to be summed up. Java thread state has the following six.

  1. NEW (new thread, there is no call to start)
  2. RUNNABLE (called start, running, or waiting for the operating system scheduling, allocation of CPU time slice)
  3. BLOCKED (synchronied,等待monitorEnter)
  4. WAITING (wait, LockSupport.park will enter the state)
  5. TIMED_WAITING (with wait latency, LockSupport.parkNanos, parkUtil)
  6. TERMINATED (thread execution end)

About Concurrent, our software engineers to do is to control the correct thread transitions between these states, the so-called "we must first of its profits", complicated by a variety of tools provided by the JDK, we only understand they can use flexible and efficient, and this is my record, "eating through Java concurrency" series of articles beginning of the heart, and the king of mutual encouragement!

Guess you like

Origin juejin.im/post/5d72f75d5188255aed032abb