我们的最常用的用于创建线程的3种方法分别是继承Thread类,继承Runnable接口,继承Collable接口,接下来,我们就来一一的解释一下这三种方法。
当然我们今天只讲继承Thread类实现多线程
继承Thread类实现多线程
java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法(就是相当于主类中的main方法)
class MyThread extends Thread{
private String title;
public MyThread (String title){
this.title=title;
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(this.title+"、"+i);
}
}
}
start()方法和run()方法
定义线程的主体类之后,很自然我们就会象到产生线程的实例化对象而后调用run()方法。实际上,我们不能够直接去调用run()方法。
我们现在举一个例子,我们现在创建四个对象前两个对象我们调用run()方法来进行运行,后两个我们调用我们的start()方法来运行。
public class TestThread {
public static void main(String[] args) {
MyThread mt1=new MyThread("thread1");
MyThread mt2=new MyThread("thread2");
MyThread mt3=new MyThread("thread3");
MyThread mt4=new MyThread("thread4");
mt1.run();
mt2.run();
mt3.start();
mt4.start();
}
}
我们现在来看一下运行的结果,看看我们从我们的运行结果中能得到什么
thread1、0
thread1、1
thread1、2
thread1、3
thread1、4
thread1、5
thread1、6
thread1、7
thread1、8
thread1、9
thread2、0
thread2、1
thread2、2
thread2、3
thread2、4
thread2、5
thread2、6
thread2、7
thread2、8
thread2、9
thread3、0
thread3、1
thread3、2
thread4、0
thread3、3
thread3、4
thread3、5
thread3、6
thread3、7
thread3、8
thread3、9
thread4、1
thread4、2
thread4、3
thread4、4
thread4、5
thread4、6
thread4、7
thread4、8
thread4、9
Process finished with exit code 0
我们通过观察可以发现,我们的现在的运行,通过run方法进行运行的是顺序执行的,只有通过我们的start方法调用的才会出现我们想要的交替执行。
那么我们的问题来了,为什么要通过start()方法来调用我们的run()而不是直接run()进行运行呢?
我们来具体的看一下我么的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 */
}
}
}
1,首先我们看到在start()方法中抛出一个IllegalThreadSataeException异常,按照原有的处理方式,应当在处进行异常处理,而此处没有处理也不会报错,因此是一个RunTimeException,这个异常的产生只是因为你重复启动线程才会产生的。所以。每一个线程对象只能够启动一次
2、下面我们看到了start()方法中调用了start0()方法,而这个方法只是一个声明而未实现同时使用native关键字进行定义
private native void start0();
native指的是调用本机的原声系统函数。
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));
} s
tatic JNINativeMethod methods[] = {
{"start0", "()V",(void \*)&JVM_StartThread}, //start0 方法
{"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中,有如下代码段:
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”!
...
}
所以我们知道Java线程创建的调用流程
这个图是帮助我们理解为什么我们需要通过我们的start()方法来调用我们的run方法,而不是我们手工的进行调用。
我们通过start()方法调用我们的run方法的时候,我们的JVM虚拟机会帮助我们加载很多多线程所需要的资源,如果我们通过手工调用的话,我们的多线程将毫无意义,和我们的普通方法是一样的。