一、Thread的run()与start()
Java的线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
二、Thread的五种状态
在Java当中,线程通常都有五种状态:创建、就绪、运行、阻塞和死亡
- 第一是创建状态:在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
- 第二是就绪状态:当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 第三是运行状态:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
- 第四是阻塞状态:线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
第五是死亡状态:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
三、实现并启动线程有两种方法
- 写一个类继承自Thread类,重写run方法。用start方法启动线程
- 写一个类实现Runnable接口,实现run方法。用new Thread(Runnable target).start()方法来启动
查看Thread源码,我们可以发现 Thread 类也实现了Runnable接口
public class Thread implements Runnable {
......
@Override
public void run() {
if (target != null) {
target.run(); // private Runnable target;
}
}
......
}
四、多线程原理
相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start是排队,等CPU选中你就是轮到你,你就run(),当CPU的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。
调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。先调用start后调用run,这么麻烦,为什么不直接调用run?就是为了实现多线程的优点,没这个start不行。
start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, run方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法如果当作普通方法的方式调用,程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发
4.1 调用start()方法是并发执行
public class Client {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
int a = 1;
while(a<=5) {
System.out.println("thread excute..........."+a);
a++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//调用start()方法是并发执行
t.start();
for(int i = 1;i<=10;i++) {
System.out.println("i = "+i);
}
}
}
打印结果
i = 1
thread excute...........1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
thread excute...........2
thread excute...........3
thread excute...........4
thread excute...........5
4.2 调用run()方法是顺序执行
public class Client {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
int a = 1;
while(a<=5) {
System.out.println("thread excute..........."+a);
a++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//调用run()方法是顺序执行
t.run();
for(int i = 1;i<=10;i++) {
System.out.println("i = "+i);
}
}
}
打印结果
thread excute...........1
thread excute...........2
thread excute...........3
thread excute...........4
thread excute...........5
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
五、run()方法是怎么被执行的
5.1 Java线程和OS线程
我们知道,Java的一个线程实际上是对应了操作系统的一个线程。而操作系统实现线程有三种方式:
- 内核线程实现
- 用户线程实现
- 用户线程加轻量级进程混合实现
Java线程在JDK1.2之前,是基于用户线程实现的。而在JDK1.2中,线程模型替换为基于操作系统原生线程模型来实现。而在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没法达成一致。对于Sun JDK来说,它的Windows版本和Linux版本都是使用一对一的线程模型实现的,一条Java线程映射到一条轻量级进程之中。
注:具体参见 «深入理解 JAVA虚拟机»第二版 第12章 Java内存模型与线程(378页)
5.2 Java线程创建
创建方式
public class ThreadTest {
class ThreadExtend extends Thread{
@Override
public void run() {
super.run();
System.out.println("ThreadExtend run......");
}
}
class RunnableExtend implements Runnable{
@Override
public void run() {
System.out.println("RunnableExtend run......");
}
}
public void testThread() {
Thread thread = new ThreadExtend();
thread.start();
Thread thread2 = new Thread(new RunnableExtend());
thread2.start();
}
}
我们看到,无论以哪种方式创建,最终我们都会重写一个叫做 run 的方法,来处理我们的业务逻辑,然而我们都是调用一个start方法,来启动一个线程;那 start方法和run方法之间是一个什么关系呢?从后边的介绍我们将获得这样一个信息:run就是一个回调函数,和我们普通的函数没有区别。
5.3 Java线程的实现
一个 Java 线程的创建本质上就对应了一个本地线程(native thread)的创建,两者是一一对应的。关键问题是:本地线程执行的应该是本地代码,而 Java 线程提供的线程函数(run)是 Java 方法,编译出的是 Java 字节码,所以, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。我们查看Thread源码可以看到:
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对象初始化的时候完成的,我们同样来看Thread源码
public
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
2 Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ //registerNatives
3 (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
4 }
5 static JNINativeMethod methods[] = {
6 {"start0", "()V",(void *)&JVM_StartThread}, //start0 方法
7 {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
8 {"isAlive","()Z",(void *)&JVM_IsThreadAlive},
9 {"suspend0","()V",(void *)&JVM_SuspendThread},
10 {"resume0","()V",(void *)&JVM_ResumeThread},
11 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority},
12 {"yield", "()V",(void *)&JVM_Yield},
13 {"sleep","(J)V",(void *)&JVM_Sleep},
14 {"currentThread","()" THD,(void *)&JVM_CurrentThread},
15 {"countStackFrames","()I",(void *)&JVM_CountStackFrames},
16 {"interrupt0","()V",(void *)&JVM_Interrupt},
17 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted},
18 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock},
19 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads},
20 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads},
21 };
观察上边一小段代码,可以容易的看出 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(), //看这里!
vmSymbolHandles::void_method_signature(),THREAD);
}
可以看到调用了 vmSymbolHandles::run_method_name 方法,而run_method_name是在 vmSymbols.hpp 用宏定义的:
class vmSymbolHandles: AllStatic {
...
template(run_method_name,"run") //这里决定了调用的方法名称是 “run”!
...
}