Java Thread源码分析

线程里面有什么成员

//要执行的业务
private Runnable target;
//线程组
private ThreadGroup group;
//线程名称
private volatile String name;
//优先级
private int priority;
//线程id ,JVM层面的,并不是操作系统的PID
private long tid;
//线程状态,JVM层面的
private volatile int threadStatus = 0;
//线程的类加载器
private ClassLoader contextClassLoader;

线程组

线程组ThreadGroup表示一组线程的集合,一旦一个线程归属到一个线程组之中后,就不能再更换其所在的线程组。那么为什么要使用线程组呢?个人认为有以下的好处:方便统一管理,线程组可以进行复制,快速定位到一个线程,统一进行异常设置等。

线程状态

Java 的线程状态分为了六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

public enum State {
	 NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
}
public State getState() {
    // get current thread state
    return sun.misc.VM.toThreadState(threadStatus);
}

下面图为线程状态切换:


Thread构造函数

    public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
    }
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

  private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        this.name = name;
        this.group = g;
        Thread parent = currentThread();
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
       // 关于父线程的ThreadLocal,后面会讲
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
     // 线程ID生成策略
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber; //自增
    }

从构造函数我们得知:

  • parent就是当前执行线程。
  • 一些值都是取得父线程的值。
  • name的生成策略:优先取客户端传入的值,否则 "Thread-"+nextThreadNum()
  • 线程ID生成策略为自增


start方法

start为启动一个线程,启动后就执行客户端传入的Runnable任务。

public synchronized void start() {
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            // ...
        }
    }
    private native void start0();

我们看到调用启动方法实际上是调用一个本地方法START0()来启动一个线程,首先START0()这个方法是在线程的静态块中来注册的,代码如下

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

这个registerNatives的作用是注册一些本地方法提供给Thread类来使用,比如start0(),isAlive(),currentThread(),sleep();这些都是大家很熟悉的方法.registerNatives
的本地方法的定义在文件 Thread.c
Thread.c定义了各个操作系统平台要用的关于线程的公共数据和操作,以下是Thread.c的全部内容

https://github.com/unofficial-openjdk/openjdk/blob/jdk/jdk/src/java.base/share/native/libjava/Thread.c

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},
};
#undef THD
#undef OBJ
#undef STE
#undef STR
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

从这段代码可以看出,start0(),实际会执行JVM_StartThread方法,这个方法是干嘛的呢?从名字上来看,似乎是在JVM层面去启动一个线程,如果真的是这样,那么在JVM层面,一定会调用Java中定义的运行方法。那接下来继续去找找答案。我们找到jvm.cpp这个文件;这个文件需要下载hotspot的源码才能找到。

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
...
native_thread = new JavaThread(&thread_entry, sz);
...

JVM_ENTRY是用来定义JVM_StartThread函数的,在这个函数里面创建了一个真正和平台有关的本地线程。本着打破砂锅查到底的原则,继续看看newJavaThread做了什么事情,继续寻找JavaThread的定义
在hotspot的源码中thread.cpp文件中1558行的位置可以找到如下代码

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
  if (TraceThreadEvents) {
    tty->print_cr("creating thread %p", this);
  }
  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);
  _safepoint_visible = false;
  // The _osthread may be NULL here because we ran out of memory (too many threads active).
  // We need to throw and OutOfMemoryError - however we cannot do this here because the caller
  // may hold a lock and all locks must be unlocked before throwing the exception (throwing
  // the exception consists of creating the exception object & initializing it, initialization
  // will leave the VM via a JavaCall and then all locks must be unlocked).
  //
  // The thread is still suspended when we reach here. Thread must be explicit started
  // by creator! Furthermore, the thread must also explicitly be added to the Threads list
  // by calling Threads:add. The reason why this is not done here, is because the thread
  // object must be fully initialized (take a look at JVM_Start)
}

这个方法有两个参数,第一个是函数名称,线程创建成功之后会根据这个函数名称调用对应的函数;第二个是当前进程内已经有的线程数量。最后我们重点关注与一下os :: create_thread,实际就是调用平台创建线程的方法来创建线程。
接下来就是线程的启动,会调用Thread.cpp文件中的Thread :: start(Thread * thread)方法,代码如下

void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

启动方法中有一个函数调用:os :: start_thread(thread);,调用平台启动线程的方法,最终会调用Thread.cpp文件中的JavaThread :: run()方法

// The first routine called by a new Java thread
void JavaThread::run() {
  // initialize thread-local alloc buffer related fields
  this->initialize_tlab();
  // used to test validitity of stack trace backs
  this->record_base_of_stack_pointer();
  // Record real stack base and size.
  this->record_stack_base_and_size();
  // Initialize thread local storage; set before calling MutexLocker
  this->initialize_thread_local_storage();
  this->create_stack_guard_pages();
  this->cache_global_variables();
  // Thread is now sufficient initialized to be handled by the safepoint code as being
  // in the VM. Change thread state from _thread_new to _thread_in_vm
  ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
  assert(JavaThread::current() == this, "sanity check");
  assert(!Thread::current()->owns_locks(), "sanity check");
  DTRACE_THREAD_PROBE(start, this);
  // This operation might block. We call that after all safepoint checks for a new thread has
  // been completed.
  this->set_active_handles(JNIHandleBlock::allocate_block());
  if (JvmtiExport::should_post_thread_life()) {
    JvmtiExport::post_thread_start(this);
  }
  EventThreadStart event;
  if (event.should_commit()) {
     event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
     event.commit();
  }
  // We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner();
  // Note, thread is no longer valid at this point!
}

这个方法中主要是做一系列的初始化操作,最后有一个方法thread_main_inner,接下来看看这个方法的逻辑是什么样的

void JavaThread::thread_main_inner() {
  assert(JavaThread::current() == this, "sanity check");
  assert(this->threadObj() != NULL, "just checking");
  // Execute thread entry point unless this thread has a pending exception
  // or has been stopped before starting.
  // Note: Due to JVM_StopThread we can have pending exceptions already!
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {
    {
      ResourceMark rm(this);
      this->set_native_thread_name(this->get_thread_name());
    }
    HandleMark hm(this);
    this->entry_point()(this, this);
  }
  DTRACE_THREAD_PROBE(stop, this);
  this->exit(false);
  delete this;
}

和主流程无关的代码咱们先不去看,直接找到最核心的代码块this-> entry_point()(this,this);,这个entrypoint应该比较熟悉了,因为我们在前面提到了,在:: JavaThread这个方法中传递的第一个参数,代表函数名称,线程启动的时候会调用这个函数。
如果大家还没有晕车的话,应该记得我们在jvm.cpp文件中看到的代码,在创建native_thread = newJavaThread( &thread_entry,SZ); 的时候传递了一个threadentry函数,所以我们在jvm.cpp中找到这个函数的定义如下

static void thread_entry(JavaThread* thread, TRAPS) {
{
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(), //注意这里
                          vmSymbols::void_method_signature(),
                          THREAD);
}

可以看到vmSymbols :: run_method_name()这个调用,其实就是通过回调方法调用Java线程中定义的运行方法,run_method_name是一个宏定义,在vmSymbols.hpp文件中可以找到如下代码

#define VM_SYMBOLS_DO(template, do_alias)    
...
template(run_method_name, "run")  
...

结论

java的一个线程实际上是对应了操作系统的一个线程。我们可以用代码验证下:

public class App {
	public static void main(String[] args) {
		new Thread() {
			public void run() {
				while(true) {
				}
			};
		}.start();
	}
}

放到linux下javac,java执行后,开启另一个终端执行:

[root@VM-0-3-centos ~]# jps
24709 Jps
24557 App
[root@VM-0-3-centos ~]# top -Hp 24557
top - 12:49:19 up 19 days, 20:45,  2 users,  load average: 0.40, 0.11, 0.08
Threads:  13 total,   1 running,  12 sleeping,   0 stopped,   0 zombie
%Cpu(s): 50.5 us,  0.3 sy,  0.0 ni, 49.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3880184 total,   402772 free,   396916 used,  3080496 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  3200200 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                              
24569 root      20   0 1293248  17380   7408 R 99.7  0.4   0:27.80 java                                                                                                                 
24568 root      20   0 1293248  17380   7408 S  0.3  0.4   0:00.01 java                                                                                                                 
24557 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24558 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.04 java                                                                                                                 
24559 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24560 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24561 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24562 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24563 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24564 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24565 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24566 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java                                                                                                                 
24567 root      20   0 1293248  17380   7408 S  0.0  0.4   0:00.00 java

可以看到 PID 为 24569 就是我们的main线程,对应linux实际是一个进程。

查看main线程堆栈:

[root@VM-0-3-centos ~]# pstack 24569
Thread 1 (process 24569):
#0  0xe75b6164 in ?? ()
#1  0xe75a3459 in ?? ()
#2  0xf6c5bc9f in JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#3  0xf6e993d9 in os::os_exception_wrapper(void (*)(JavaValue*, methodHandle*, JavaCallArguments*, Thread*), JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#4  0xf6c5c0c2 in JavaCalls::call_virtual(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*) () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#5  0xf6c5c4fb in JavaCalls::call_virtual(JavaValue*, Handle, KlassHandle, Symbol*, Symbol*, Thread*) () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#6  0xf6ce90cd in thread_entry(JavaThread*, Thread*) () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#7  0xf6fc7569 in JavaThread::thread_main_inner() () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#8  0xf6fc775e in JavaThread::run() () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#9  0xf6ea0249 in java_start(Thread*) () from /usr/local/java8/jre/lib/i386/server/libjvm.so
#10 0xf772cbbc in start_thread () from /lib/libpthread.so.0
#11 0xf763f1fe in clone () from /lib/libc.so.6

run方法

start后JVM会回调这个run方法,run中执行我们的target Runnable 。

    public void run() {
        if (target != null) {
            target.run();
        }
    }

stop方法(已过时)

public final void stop() {
        // A zero status value corresponds to "NEW", it can't change to
        // not-NEW because we hold the lock.
        if (threadStatus != 0) {
            resume(); // Wake up thread if it was suspended; no-op otherwise
        }

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }
private native void stop0(Object o);

stop过时的原因:

  • stop方法是过时的从Java编码规则来说,已经过时的方法不建议采用。
  • stop方法会导致代码逻辑不完整stop方法是一种“恶意”的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的。看如下的代码:
public class App {
	public static void main(String[] args) throws Exception {
		// 子线程
		Thread thread = new Thread() {
			@Override
			public void run() {
				System.out.println("开始执行");
				try {
					// 子线程休眠一秒
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// 异常处理
					System.out.println("===============");
				}
				System.out.println("收尾逻辑");
			}
		};
		// 启动线程
		thread.start();
		// 主线程休眠一秒
		Thread.sleep(500);
		// 子线程停止
		thread.stop();
		System.out.println("暴力停止");
	}
}

----------
开始执行
暴力停止

JVM在执行thread.stop()时,子线程还在执行sleep(1000),此时stop方法会清除栈内信息,结束该线程,这也就导致了run方法的逻辑不完整,收尾逻辑 还没执行,可能非常重要,比如子线程的主逻辑、资源回收、情景初始化等,但是因为stop线程了,这些就都不再执行,于是就产生了业务逻辑不完整的情况。

  • stop方法会破坏原子逻辑多线程为了解决共享资源抢占的问题,使用了锁概念,避免资源不同步,但是正因此原因,stop方法却会带来更大的麻烦: 它会丢弃所有的锁 ,导致原子逻辑受损。

如何正确停止线程? isInterrupted + interrupt配合退出Runnable 。

isInterrupted 方法

测试线程是否已被中断。 根据传递的ClearInterrupted值重置或不重置中断状态。

    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    private native boolean isInterrupted(boolean ClearInterrupted);

interrupt 方法

设置当前线程中断状态,isInterrupted方法就会返回true,表示已中断。

如果这个线程在调用object类的wait()、wait(long)或wait(long, int)方法,或者join()、join(long, int)、join(long, int)、sleep(long)或sleep(long, int)方法时被阻塞,那么它的中断状态为true,并将收到一个 InterruptedException

    public void interrupt() {
        interrupt0();
    }
    private native void interrupt0();

isAlive方法

测试该线程是否处于活动状态。 如果一个线程已经启动并且还没有死,那么它就是活的。

public final native boolean isAlive();

join方法

等待该线程,直到该线程结束。内部靠Object.wait实现等待。

    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        // 传入0的话,无线等待,直到isAlive返回false
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {  //线程活着
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

强烈推荐一个 进阶 JAVA架构师 的博客

JAVA架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者