Java & Go 线程模式对比

2023年 9月 30日 39.2k 0

引言

Java 中的 CPU 资源分配对象是 Thread,Go 中的 CPU 资源分配对象是 goroutine。Java Thread 与操作系统的线程是一一对应的关系;goroutine 是 Go实现的用户轻量级线程,通过 GPM 进行管理,与操作系统是 n:m 的关系。

本文旨在通过剖析源码,研究 Java 和 Go 中的线程模型具体的实现和设计思想。

Java Thread

Hotspot JavaThread

在 Hotspot VM 中,Java 中的java.lang.Thread 和操作系统 Thread 是 1:1 的关系,Java 线程会在 start 的时候创建对应的操作系统线程,在Java线程终止的时候回收操作系统线程。

Hotspot 中对与 thread 相关的代码在 jdk/src/hotspot/share/runtime 中,Java11 的JavaThread 是定义在 thread.hppthread.cpp 中,源码可以看 jdk-11+28 hotspot runtime

// Class hierarchy
// - Thread
//   - NamedThread
//     - VMThread
//     - ConcurrentGCThread
//     - WorkerThread
//       - GangWorker
//       - GCTaskThread
//   - JavaThread
//     - various subclasses eg CompilerThread, ServiceThread
//   - WatcherThread

// Thread
class Thread: public ThreadShadow {
  friend class VMStructs;
  friend class JVMCIVMStructs;
 private:
	GCThreadLocalData _gc_data; // GC 中的 ThreadLocal 数据
 protected:
	void*       _real_malloc_address; // 真实分配地址
 protected:
  OSThread* _osthread; // 和 Thread 关联的 os Thread
}

// JavaThread
class JavaThread: public Thread {
	friend class VMStructs;
  friend class JVMCIVMStructs;
  friend class WhiteBox;
 private:
  JavaThread*    _next;            // Threads列表中的下一个 thread
  bool           _on_thread_list;  // 当前 JavaThread 是否在Thread列表中
  oop            _threadObj;       // Java级别的线程对象
}

JavaThread::JavaThread(bool is_attaching_via_jni) :
                       Thread() {
  initialize();
  if (is_attaching_via_jni) {
    _jni_attach_state = _attaching_via_jni;
  } else {
    _jni_attach_state = _not_attaching_via_jni;
  }
  assert(deferred_card_mark().is_empty(), "Default MemRegion ctor");
}

Java11 的 Hotspot 中,会有一个父类 Thread,Thread 中定义 _osThread 用于关联操作系统线程,针对不同父类会有以下子类实现 ( HotSpot Runtime Overview #Thread Management ):

  • NamedThread:具有名字的、代表唯一命名实例的线程,定义了 _name 表示线程名
    • VMThread:负责执行 VM 操作的线程,如 scavenge, garbage_collect 等
    • ConcurrentGCThread:负责并发执行垃圾回收的线程
    • WorkerThread:指代具有分配工作ID的工作线程,_id 作为具体的工作ID
      • GangWorker:字面理解是帮派工作线程
      • GCTaskThread:负责GC任务的工作线程
  • JavaThread:对应 java.lang.Thread
    • CompilerThread:负责运行时,将编译后的字节码转换成机器码 (native thread) 的线程
    • ServiceThread:用于低内存检测和JVMTI (虚拟机提供的native接口)
  • WatcherThread:负责模拟时钟中断 (simulate timer interrupts)
public:
  // Constructor
  JavaThread();                            // delegating constructor
  JavaThread(bool is_attaching_via_jni);   // for main thread and JNI attached threads
  JavaThread(ThreadFunction entry_point, size_t stack_size = 0);
  ~JavaThread();

JavaThread::JavaThread(bool is_attaching_via_jni) :
                       Thread() {
  initialize();
  if (is_attaching_via_jni) {
    _jni_attach_state = _attaching_via_jni;
  } else {
    _jni_attach_state = _not_attaching_via_jni;
  }
  assert(deferred_card_mark().is_empty(), "Default MemRegion ctor");
}

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
                       Thread() {
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
}

具体看 JavaThread 的 Constructor,我们可以看到它有两种构造方式,其中 JavaThread(bool is_attaching_via_jni) 仅用于主线程和JNI的连接。故我们所使用的用户线程构造方法是 JavaThread(ThreadFunction entry_point, size_t stack_size = 0) ,这里对 _osthread 进行了创建和绑定,这也决定了在 Hotspot 虚拟机中,Java Thread 与 操作系统线程是一一对应的关系。

Thread JNI

Hotstop 虚拟机中用c++定义了 JavaThread, 对应 JDK 中的 java 定义的 Thread 类,从java调用C++ 使用的是JNI (Java Native Interface)。那么JNI是如何将这两处代码连接起来的?

下面通过源码看看,从创建Java中的Thread对象,到创建VM 中的 JavaThread 是怎样的过程

public class Thread implements Runnable {

    public synchronized void start() {
	if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            // native 方法 创建 
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
		
    private native void start0();
}

这部分可以看到,在Thread.start() 方法中,会调用 start0() 的native方法去创建一个操作系统线程。换句话说,只是 new Thread()仅仅是在 Java程序中创建了thread对象,并没有对应的系统资源,只有调用了start方法后才会在虚拟机中创建对应的资源。JDK在jdk/src/java.base/share/native/libjava/Thread.c 中定义了Java中native方法所对应vm 中的 function。

需要注意的是 JNI 的定义是在JDK中用c实现的,但是并未在虚拟机的源码中,这意味着如果不使用Hotspot虚拟机,我只需要在 openjdk 的基础上,在新的虚拟机中实现 JVM_StartThread 方法就行

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

那么 Hotstop 虚拟机 中又是如何实现 JVM_StartThread 的,这里我们需要看 jdk/src/hotspot/share/prims/jvm.cpp

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
  bool throw_illegal_thread_state = false;
  // We must release the Threads_lock before we can post a jvmti event
  // in Thread::start.
  {
    MutexLocker mu(Threads_lock);

    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      NOT_LP64(if (size > SIZE_MAX) size = SIZE_MAX;)
      size_t sz = size > 0 ? (size_t) size : 0;

			// 创建 Hotstop 中的 JavaThread,同时创建对应的 _osthread
      native_thread = new JavaThread(&thread_entry, sz);
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }

  assert(native_thread != NULL, "Starting null thread?");

  if (native_thread->osthread() == NULL) {
    native_thread->smr_delete();
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        os::native_thread_creation_failed_msg());
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              os::native_thread_creation_failed_msg());
  }

	// 开始执行,执行 os::start_thread
  Thread::start(native_thread);

JVM_END

我们总结一下上面的链路

Thread::start() →

native start0() → JNI JNINativeMethod →

JVM_StartThread → Hotstop JavaThread → osthread

线程状态 JMVTI Thread State

JDK中的 Thread.class 和 c++ 代码中的 JavaThread 是一一对应的,线程具体的执行、流转等也是在虚拟机处理的,所以线程状态 (thread status) 在 Hotstop 虚拟机也需要有对应的地方,即JMVTI Thread State,对应的源码是 jvmtiThreadState.hpp

  • NEW → JVMTI_THREAD_STATE_ALIVE : 0x0001
  • RUNNABLE → JVMTI_THREAD_STATE_RUNNABLE : 0x0004
  • BLOCKED → JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER : 0x0400
  • WAITING → JVMTI_THREAD_STATE_WAITING_INDEFINITELY : 0x0010
  • TIMED_WAITING → JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT : 0x0020
  • TERMINATED → JVMTI_THREAD_STATE_TERMINATED : 0x0002
public class Thread implements Runnable {
    /*
     * Java thread status for tools, default indicates thread 'not yet started'
     */
    private volatile int threadStatus;

    public enum State {
				// Thread state for a thread which has not yet started.
        NEW,

        // Thread state for a runnable thread.
        RUNNABLE,

        // Thread state for a thread blocked waiting for a monitor lock.
        BLOCKED,

        // Thread state for a waiting thread.
        WAITING,

        // Thread state for a waiting thread with a specified waiting time.
        TIMED_WAITING,

        // The thread has completed execution.
        TERMINATED;
    }
}

public class VM {
    /* The threadStatus field is set by the VM at state transition
     * in the hotspot implementation. Its value is set according to
     * the JVM TI specification GetThreadState function.
     */
    private static final int JVMTI_THREAD_STATE_ALIVE = 0x0001;
    private static final int JVMTI_THREAD_STATE_TERMINATED = 0x0002;
    private static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004;
    private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
    private static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010;
    private static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020;

    public static Thread.State toThreadState(int threadStatus) {
        if ((threadStatus & JVMTI_THREAD_STATE_RUNNABLE) != 0) {
            return RUNNABLE;
        } else if ((threadStatus & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0) {
            return BLOCKED;
        } else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0) {
            return WAITING;
        } else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0) {
            return TIMED_WAITING;
        } else if ((threadStatus & JVMTI_THREAD_STATE_TERMINATED) != 0) {
            return TERMINATED;
        } else if ((threadStatus & JVMTI_THREAD_STATE_ALIVE) == 0) {
            return NEW;
        } else {
            return RUNNABLE;
        }
    }
}

Untitled.png

线程池

ThreadPoolExecutor

Java官方库在 JDK 中提供线程池 ThreadPoolExecutor,用于线程复用和线程管理。关于 ThreadPoolExecutor 已经有很多优秀的文章介绍了,这里仅仅强调几个比较重要的参数和线程池的执行流程

ThreadPoolExecutor 线程池创建时有以下3个比较重要的参数:

  • corePoolSize:核心线程池数,指的是最小保持活跃线程的数量
  • maximumPoolSize:线程池允许创建的最大线程数量,当线程池的任务队列满了之后,可以创建的最大线程数
  • workQueue:线程池存放任务的队列,用来存储线程池的所有待执行任务

同时还有一些内部参数至关重要:

  • AtomicInteger ctl:主控制参数,记录了线程池的状态和工作线程数量。ctl是一个32位的二进制(包含符号位),去掉符号位有31位,线程池中使用后29位来表示工作线程数,最多可以计数5亿个;用前3位 (包含符号位) 表示线程池的状态。对 ctl 的修改通过 CAS + volatile 保证其线程一致性.
  • HashSet workers:正在运行的工作线程,必须在获取到 mainLock 时才能访问,数量和 ctl 中存储的数量是相同的
  • ReentrantLock mainLock:用于线程池的锁,对线程池中的 workers 进行访问时,需要先获得此锁,注意这是一个非公平锁

线程池的的执行流程如下:

  • 如果当前的工作线程数小于核心线程池数 corePoolSize,则尝试创建工作线程并执行任务。如果成功则返回;否则进入2
  • 尝试将任务添加至等待队列 workQueue,添加成功则二次检查 (从第1步检查后可能有死亡的),如果二次检查发现没有正在运行的线程,则创建工作线程并执行;否则进入3
  • 添加队列失败,尝试直接创建工作线程,如果当前运行的线程数量未超过maximumPoolSize,则创建工作线程并执行;否则执行拒绝策略 handler
  • public class ThreadPoolExecutor extends AbstractExecutorService {
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private final ReentrantLock mainLock = new ReentrantLock();
    private final HashSet workers = new HashSet();

    private volatile int corePoolSize;
    private volatile int maximumPoolSize;
    private final BlockingQueue workQueue;
    private volatile long keepAliveTime;
    private volatile ThreadFactory threadFactory;
    private volatile RejectedExecutionHandler handler;

    private static final int COUNT_BITS = Integer.SIZE - 3; // 29
    // 11111...111 2 ^ 29 - 1
    private static final int COUNT_MASK = (1

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论