小言_互联网的博客

Thread的Start和Run方法区别详解

218人阅读  评论(0)

Thread的Start和Run方法区别

创建线程的两种方式,继承Thread类和实现Runnable接口。

1.继承Thread类

class ThreadDemoExtends extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("ThreadExtneds Name: " + Thread.currentThread().getName() 
					+ ", threadNo : " + i);
		}
	}
}

2.实现Runnable接口

class ThreadDemoRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("ThreadRunnable Name: " + Thread.currentThread().getName()
					+ ", threadNo : " + i);
		}
	}
}

我们看到,无论以哪种方式创建,最终我们都会重写一个叫做 run 的方法,来处理我们的业务逻辑。
当我们使用创建的线程时,

public class Test00 {
	public static void main(String[] args) {
		System.err.println("主线程开始...........");
		ThreadDemoExtends threadDemo01 = new ThreadDemoExtends();
		Thread threadDemo02 = new Thread(new ThreadDemoRunnable());
		threadDemo01.run();
		threadDemo02.run();
		System.err.println("主线程结束...........");
	}
}

结果:

可以看到无论是哪一种创建的方式,run方法都没有创建新的进程,仍然是使用主线程
当使用start方法

public class Test00 {

	public static void main(String[] args) {
		System.err.println("主线程开始...........");
		ThreadDemoExtends threadDemo01 = new ThreadDemoExtends();
		Thread threadDemo02 = new Thread(new ThreadDemoRunnable());
		threadDemo01.start();
		threadDemo02.start();
		System.err.println("主线程结束...........");
	}
}


可以看到都创建了新的线程

看看run方法,
如果是继承Thread类,会重写run()方法,执行时,执行我们继承类重写的run()方法.
如果是实现Runnable接口,创建Thread时,会调用

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);

在init中通过

 this.target = target;

把Runnable的实现类赋值给target,当使用run方法时```java

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

会调用我们实现时写的run()方法.
因此,run方法只是调用了一个普通方法,并没有启动另一个线程,程序还是会按照顺序执行相应的代码

因此,无论使用哪种方式创建线程, run方法都是用来处理线程对应的业务逻辑,
来启动一个线程需要通过start方法;

而start方法如何去启动线程,并调用run方法,是比较复杂的,以下为转载,介绍了原理:

转载自:Java 中的进程与线程

一个 Java 线程的创建本质上就对应了一个本地线程(native thread)的创建,两者是一一对应的。
关键问题是:本地线程执行的应该是本地代码,而 Java 线程提供的线程函数(run)是 Java 方法,编译出的是 Java 字节码,
所以, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。

jdk中thred类start方法的源码:

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            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 */
            }
        }
    }

可以看到它实际上调用了本地方法 start0, 而start0声明如下:

private native void start0();

也就是新创建的线程启动调用native start0方法,而这些native方法的注册是在Thread对象初始化的时候完成的,

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

Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 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}, 
    {"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},

观察上边一小段代码,可以容易的看出 Java 线程调用 start->start0 的方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎么处理的呢?

实际上,我们需要看到的是该方法最终要调用 Java 线程的 run 方法,事实的确也是这样的。

在 jvm.cpp 中,

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(), 
vmSymbolHandles::void_method_signature(),THREAD); 
}

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

template(run_method_name,"run") 

Java 线程创建调用关系图

综上所述,首先 , Java 线程的 start 方法会创建一个本地线程(通过调用 JVM_StartThread),该线程的线程函数是定义在 jvm.cpp 中的 thread_entry,由其再进一步调用 run 方法。可以看到 Java 线程的 run 方法和普通方法其实没有本质区别,直接调用 run 方法不会报错,但是却是在当前线程执行,而不会创建一个新的线程。


转载:https://blog.csdn.net/thaddeuslu/article/details/101454135
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场