Java并发编程:Thread.java源码解析

Java并发编程:Thread.java源码解析

1.前言

  最近发现对于线程还不是很熟悉只是停于理解,便对Thread.java类做一个系统的研究,并记录在此。

2.正文

2.1.案例解析

先看如下三个例子:

    @Test
    public void test03() throws InterruptedException {
        new Thread(new MyRunnable()).start();
        new MyThread().start();
        new Thread(new FutureTask<>(new MyCallable())).start();
    }

    public static
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("I am MyRunnable.");
        }
    }

    public static
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("I am MyThread.");
        }
    }

    public static
    class MyCallable implements Callable<Object> {
        @Override
        public Object call() throws Exception {
            System.out.println("I am MyCallable.");
            return null;
        }
    }

执行结果
案例执行结果
从案例分析,我们可以了解到任何线程的实现方式都离不开Thread.java,由此可以该类的重要性(ps:除非自己再去构造一个Thread.java)。从某种意义上来讲,线程的实现方式有三种,分别是:

  • 继承Thread.java,覆盖run()方法
  • 实现Runnable接口
  • 实现Callable接口,注意:实现该接口之后,还需要构造一个FutureTask对象,再把FutureTask对象传入Thread.java

仔细观察以上三个接口,我们就会发现FutureTask.java其本质是实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口,所以其本质上是Runnable的一个子集。为什么会有这个接口呢?原因很简单,我们需要该方法的返回值。

2.2.源码解析

我们先从Thread.java的执行顺序来分析,首先需要注意的是Thread.java中有一个静态代码块,如下:

private static native void registerNatives();
static {
    registerNatives();
}

注意该方法标记了native,意思就是该方法会通过JNI调用本地方法,例如C/C++的方法。registerNatives()方法主要的作用就是注册一些本地方法供Thread.java使用,如start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的。这个方法放在一个static语句块中,当该类被加载到JVM中的时候,它就会被调用,进而注册相应的本地方法。而本地方法registerNatives()是定义在Thread.c文件中的。
Thread.c是个很小的文件,它定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如下:

  JNIEXPORT void JNICALL
  Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ //registerNatives
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
  }
  
  static JNINativeMethod methods[] = {
      {"start0", "()V",(void *)&JVM_StartThread}, //start0 方法
      {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, //stop0 方法
      {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, //isAlive 方法
      {"suspend0","()V",(void *)&JVM_SuspendThread}, //suspend0 方法
      {"resume0","()V",(void *)&JVM_ResumeThread}, //resume0 方法
      {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, //setPriority0 方法
      {"yield", "()V",(void *)&JVM_Yield}, //yield 方法
      {"sleep","(J)V",(void *)&JVM_Sleep}, //sleep 方法
      {"currentThread","()" THD,(void *)&JVM_CurrentThread}, //currentThread 方法
      {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, //countStackFrames 方法
      {"interrupt0","()V",(void *)&JVM_Interrupt}, //interrupt0 方法
      {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, //isInterrupted 方法
      {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, //holdsLock 方法
      {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, //getThreads 方法
      {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, //dumpThreads 方法
};

Java_Java_lang_Thread_registerNatives()方法是JNI的命名方式,通过约束就可以准确找到并调用本地方法。methods[] 数组,每一行的参数意思如下:

  • 第一个为Java对应的方法名
  • 第二个括号里面为参数类型,括号外面为返回值
  • 第三个为C/C++对应的方法地址

有兴趣的同学可以去了解一下JNI,JNI PDF著名书籍下载,阅读后对你理解一些比较底层方法的源码有帮助。然后,我们再看其构造函数。

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

对于上述参数的含义,如下:

  • Runnable target 代表 实现Runnable接口的目标对象
  • String name 代表 线程名字,如果不设置线程名字,默认"Thread-" + nextThreadNum()
  • ThreadGroup group 代表 线程组,没有设置线程组,默认从父线程继承
  • AccessControlContext acc 代表 权限访问控制
  • long stackSize 代表 该线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。这取决于VM如何处理这个数字;一些vm会忽略它。

从Thread.java的所有构造函数中,我们可以清晰的发现,所有的构造函数都调用了init()方法。对于构造函数调用实例方法init(),有必要说明一下:

  类体中的方法分为实例方法和类方法两种,用static修饰的是类方法。
  当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址,当该类创建对象后,类中的实例方法才分配入口地址,从而实例方法可以被类创建的任何对象调用执行。需要注意的是,当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址,也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
  对于类中的类方法,在该类被加载到内存时,就分配了相应的入口地址。从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址直到程序退出才被取消。类方法在类的字节码加载到内存时就分配了入口地址。
  因此,Java语言允许通过类名直接调用类方法,而实例方法不能通过类名调用。在Java语言中,类中的类方法不可以操作实例变量,也不可以调用实例方法,这是因为在类创建对象之前,实例成员变量还没有分配内存,而且实例方法也没有入口地址。

实例对象的构造顺序是 实例变量>实例代码块>构造函数,所以在构造函数中使用实例方法init()是可以的。init()代码如下:

	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        //检查线程的name是否为空,为空则抛出异常
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

		//设置线程名字
        this.name = name;

		//返回对当前正在执行的线程对象的引用
        Thread parent = currentThread();
        //如果已经为当前应用程序建立了安全管理员,则返回该安全管理员; 否则返回null
        SecurityManager security = System.getSecurityManager();
		//检查线程组
        if (g == null) {
            //安全检查
            if (security != null) {
            	//返回要在其被调用时实例化任何正在创建的新线程的线程组。 默认情况下,它返回当前线程的线程组
                g = security.getThreadGroup();
            }

            //设置线程组
            if (g == null) {
            	//当前正在执行的线程对象的线程组
                g = parent.getThreadGroup();
            }
        }

        //检查可达性,确定当前运行的线程是否有权限修改此线程组
        g.checkAccess();

        //是否有权限访问
        if (security != null) {
        	//验证此实例(可能是子类)可以在不违反安全约束的情况下构造:子类必须不覆盖对安全敏感的非最终方法,
        	//否则检查“enableContextClassLoaderOverride”运行时权限。
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

		//往线程组添加线程但未启动
        g.addUnstarted();
		//设置线程组
        this.group = g;
        //设置是否是守护线程
        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);
        //设置InheritableThreadLocal,提供从父线程到子线程的值继承:
        //当创建子线程时,子线程将接收父线程具有值的所有可继承线程局部变量的初值。
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        //存储指定的堆栈大小
        this.stackSize = stackSize;

        //设置线程ID
        tid = nextThreadID();
    }

初始化完成后,就需要显示调用start()方法,让其进入准备状态。start()方法如下:

	public synchronized void start() {
        //如果不是准备状态,抛出异常,即一个线程只能使用一次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) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

上述代码,配上注释很容易理解。其中start0()方法是会调用本地方法,在本文最开始,我们发现

{"start0", "()V",(void *)&JVM_StartThread}

JVM_StartThread方法将会被Java调用,在 jvm.cpp 中,我们发现:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) {
    JavaThread *native_thread = NULL;
    ...
    native_thread = new JavaThread(&thread_entry, sz);
    ...
    Thread::start(native_thread);
}

在hotspot\src\share\vm\runtime\thread.cpp中查看JavaThread对象的实现:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz)  : ...
{
    ...
    os::create_thread(this, thr_type, stack_sz);
}

void Thread::start(Thread* thread) 
{
    ...
    os::start_thread(thread);
}

在hotspot\src\os目录下可以看到windows, linux, solaris和posix的实现,先检查linux\vm\os_linux.cpp:

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) 
{
    ...
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
    ...
}

通过调用平台的API创建一个线程!在回到 jvm.cpp 中,代码段:

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

这里JVM_ENTRY()是一个宏,用来定义JVM_StartThread()函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如下:

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()),
    vmSymbolHandles::run_method_name(),    //LOOK! 看这里
    vmSymbolHandles::void_method_signature(),THREAD);
 }

可以看到调用了 vmSymbolHandles::run_method_name 方法,而run_method_name是在 vmSymbols.hpp 用宏定义的:

class vmSymbolHandles: AllStatic {
   ...
    template(run_method_name,"run")  //LOOK!!! 这里决定了调用的方法名称是 “run”!
   ...
}

就是这里进行调用了Thread.java类中的run方法。而Thread.java类的run方法如下:

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

很明显,该方法又去调用target对象的run方法。至此我们理解了一个完整的Thread的创建以及运行过程。后面我将贴出比较重要的一些方法,有兴趣可以阅读源码。

	//返回当前正在运行的线程
	public static native Thread currentThread();
	//向调度程序发出的提示,表明当前线程愿意使用当前的处理器。调度程序可以忽略这个提示
	public static native void yield();
	//使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性
	public static native void sleep(long millis) throws InterruptedException;
	//测试该线程是否为活动线程。如果线程已启动但尚未死亡,则该线程为活动线程
	public final native boolean isAlive();
	//当且仅当当前线程持有指定对象上的监视器锁时,返回true
	public static native boolean holdsLock(Object obj);

值得一提的是Thread.java优先级范围是1-10。如下:

	/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

当线程执行完任务时,如何销毁呢?答案在exit()方法中:

    private void exit() {
        if (group != null) {
        	//通知组线程该线程已经终止。
            group.threadTerminated(this);
            group = null;
        }
        target = null;
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

上述代码,全部对象设置为空,可以使JVM垃圾回收器有效的进行回收,减少内存使用量。再来看看join()方法,需要提前说明的是Object.java对象的wait()方法,wait()方法使当前线程等待,直到另一个线程调用
notify()或notifyAll()方法或已经过了指定的时间(就算wait的传参值)。join()方法如下:

	public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
        	//直到该线程死亡才结束
            while (isAlive()) {
                wait(0);
            }
        } else {
        	//直到超过预期时间
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

对于线程异常的捕获可以通过setUncaughtExceptionHandler()方法来控制,如下:

	//设置在此线程因未捕获异常而突然终止时调用的处理程序
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

3.结语

学如逆水行舟,不进则退。

4.参考博客

Java线程的创建
Java Thread:揭开Run方法被调用的真正面纱

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章