Java中LockSupport.park/unpark源码分析

2023-03-15 13:51:48 浏览数 (2)

LockSupport类是java.util.concurrent包中各种锁实现的基础。了解LockSupport的内部机制,对于我们理解concurrent包中的各种锁的实现有很大帮助。

本文将从源码角度分析LockSupport.park/unpark是如何实现的。OpenJDK版本

➜ jdk hg id 76072a077ee1 jdk-11 28

首先,我们来看下LockSupport.park方法

Java类java.util.concurrent.locks.LockSupport

代码语言:javascript复制
public static void park() {
    U.park(false, 0L);
}
...
private static final Unsafe U = Unsafe.getUnsafe();

它调用了Unsafe中的park方法,看下该方法

Java类jdk.internal.misc.Unsafe

public native void park(boolean isAbsolute, long time);

该方法是个native方法,看下对应的JVM内部的代码

文件src/hotspot/share/prims/unsafe.cpp

代码语言:javascript复制
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time)) {
  ...
  thread->parker()->park(isAbsolute != 0, time);
  ...
} UNSAFE_END

该方法先调用了thread->parker()方法,获取到thread专属的parker对象,再调用parker对象的park方法实现最终的park操作。

我们先来看下thread->parker()方法返回的对象的类是什么样子

文件src/hotspot/share/runtime/park.hpp

代码语言:javascript复制
class Parker : public os::PlatformParker {
private:
  volatile int _counter ;
  ...
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();
  ...
};

该类继承了os::PlatformParker类,我们再看下这个类

文件src/hotspot/os/posix/os_posix.hpp

代码语言:javascript复制
class PlatformParker : public CHeapObj<mtInternal> {
 protected:
  enum {
    REL_INDEX = 0,
    ABS_INDEX = 1
  };
  int _cur_index;  // which cond is in use: -1, 0, 1
  pthread_mutex_t _mutex[1];
  pthread_cond_t  _cond[2]; // one for relative times and one for absolute
  ...
};

通过上面两个类,我们对Parker对象有了个大概的认识。现在我们继续上面的park方法。

在thread->parker()方法返回Parker对象后,Unsafe_Park方法又调用了其park方法,看下这个方法

文件src/hotspot/os/posix/os_posix.cpp

代码语言:javascript复制
void Parker::park(bool isAbsolute, jlong time) {
  ...
  // Don't wait if cannot get lock since interference arises from
  // unparking. Also re-check interrupt before trying wait.
  if (Thread::is_interrupted(thread, false) ||
      pthread_mutex_trylock(_mutex) != 0) {
    return;
  }
  ...
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    ...
    return;
  }
  ...
  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 = pthread_cond_timedwait(&_cond[_cur_index], _mutex, &absTime);
    ...
  }
  _cur_index = -1;


  _counter = 0;
  status = pthread_mutex_unlock(_mutex);
  ...
}

由上面的代码我们可以看到,该方法先调用了pthread_mutex_trylock方法获取锁,然后再判断_counter是否大于0,如果大于0,说明对应的unpark方法已经被调用过,可以直接返回。如果不大于0,则会根据我们传入的time参数,选择对应的pthread_cond_t变量,然后再调用pthread_cond_wait或是pthread_cond_timedwait方法进行等待,直到超时或者收到unpark方法发过来的signal告诉我们返回。

wait方法返回之后,_counter被置为0,表示上次的unpark操作被消耗掉了,如果再调用park方法还是需要等待的。最后unlock,整个方法返回。

至此,整个LockSupport.park方法已经分析完毕。

我们再来看下LockSupport.unpark方法

Java类java.util.concurrent.locks.LockSupport

代码语言:javascript复制
public static void unpark(Thread thread) {
    if (thread != null)
        U.unpark(thread);
}

该方法同样是调用了Unsafe中的unpark,看下对应方法

Java类jdk.internal.misc.Unsafe

public native void unpark(Object thread);

该方法是个native方法,看下对应的JVM内部的代码

文件src/hotspot/share/prims/unsafe.cpp

代码语言:javascript复制
UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread)) {
  Parker* p = NULL;


  if (jthread != NULL) {
    ...
    oop java_thread = NULL;
    ...
    if (java_thread != NULL) {
      ...
      if (lp != 0) {
        ...
        p = (Parker*)addr_from_java(lp);
      } else {
        ...
      }
    }
  }


  if (p != NULL) {
    ...
    p->unpark();
  }
} UNSAFE_END

该方法最终调用了对应Java线程专属的Parker对象的unpark方法,看下这个方法

文件src/hotspot/os/posix/os_posix.cpp

代码语言:javascript复制
void Parker::unpark() {
  int status = pthread_mutex_lock(_mutex);
  ...
  const int s = _counter;
  _counter = 1;
  ...
  int index = _cur_index;
  status = pthread_mutex_unlock(_mutex);
  ...
  if (s < 1 && index != -1) {
    // thread is definitely parked
    status = pthread_cond_signal(&_cond[index]);
    ...
  }
}

由上面的代码我们可以看到,该方法首先调用了pthread_mutex_lock方法获取锁,然后记录下当前的_counter和_cur_index值,在解锁之前,_counter也会被设置为1,表示unpark方法已经被调用过。

解锁之后再判断记录的_counter值和_cur_index值,如果_counter小于1则说明unpark方法之前没有被调用过,如果_cur_index不等于-1则说明park方法之前被调用过。

当这两种情况同时成立时,说明对应的Java线程已经处于wait状态,等待我们的unpark操作。所以在此次的unpark方法中,我们先根据_cur_index值,找到对应的pthread_cond_t变量,然后再调用pthread_cond_signal方法,告诉对应的park方法,它可以从wait中返回了。

至此,LockSupport.unpark方法也分析完毕。

综上所述,LockSupport.park/unpark方法本质上是用pthread的mutex and convar机制实现的。

0 人点赞