Thread与Runnable对比
java多线程的实现方式最基本的有两种,继承Thread和实现Runnable接口。但实则不然,从跟本上来说只有一种方式,那就是实例化Thread,重写其中的run方法。当然这只是准备状态,真正启动线程需要调用该线程的start方法进行启动。
- 继承Thread
public class Demo1 {
public static void main(String[] args) {
Conductor conductor = new Conductor();
conductor.start();
}
static class Conductor extends Thread{
@Override
public void run() {
System.out.println("卖票了!!!");
}
}
}
这部分应该来说很好理解,Conductor继承了Thread并重写了其中的run方法,run方法就是该线程启动后需要执行的某段代码,最后在主线程创建一个Conductor,调用start开启线程。
- 实现Runnable接口
public class Demo2 {
public static void main(String[] args) {
Thread thread = new Thread(new Conductor());
thread.start();
}
static class Conductor implements Runnable {
@Override
public void run() {
System.out.println("卖票了!!!");
}
}
}
通过实现Runnable接口,并实现run方法,最后创建实例作为Thread构造函数的参数传入同样可以实现开启新线程。
既然有两种方式,那我们使用时总该在这两种方式中做出选择,现在来看看两种实现方式的优缺点。
- 在java里面只允许进行单继承多实现,如果使用使用继承Thread的方式,那该类就无法继承其他类,而实现Runnable接口则可以避免单继承的限制。
- 实现Runnable可以实现多线程的资源共享,而继承Thread则达不到这种效果。
- 在Thread中进行线程的控制逻辑,在Runnable的实现类中运行业务逻辑,分工较为明确,解耦更彻底一些。
看到这里可能大家会有个疑问,为什么是调用Thread.start()而不是其中的run方法呢?这里可以稍微看一下其中的源码。
- Thread 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 */
}
}
}
这段代码的大体逻辑是这样:
- A zero status value corresponds to state “NEW”.通过threadStatus的值判断线程状态,若不为0说明已启动线程,执行start方法就抛出异常
- 把执行该方法的线程加入到线程的group中
- 调用另一个名为start0的方法
start最终调用了start0的方法而不是run,现在我们看一下start0方法:
private native void start0();
什么东西都没有,甚至连注解都没有,不过值得注意的是这个关键字native,就是说它是一个native方法,也叫做JNI方法,最终这个方法将进入JVM中执行。嗯?那在JVM中执行了什么,我们暂且不去研究,回到start方法,注意一下那一大串注解,开头就告诉了我们Causes this thread to begin execution; the Java Virtual Machine calls the run
method of this thread.意思就是start方法是现成的执行者,JVM会执行该线程的run方法。而我们知道start最终是调用了start0方法,start0方法又在JVM中,大体可以推测start0方法中的逻辑会直接或间接调用run()方法。
- Thread run方法分析
我们先看Thread这个类:
public class Thread implements Runnable {
...
}
从这里我们发现Thread实际上也是实现了Runnable,也就是说Thread中的run方法也是重写了Runnable的,而我们通过实现Runnable接口来实例一个线程是通过Thread的构造函数,所以这里我们可以先从Thread的构造函数进行入手。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
在需要传入Runnable的构造方法中发现,啥也没做,只是调用了另一个方法init,下面看一下找到最终调用的init.这里面有一行代码
this.target = target;
而this。target中的target正是Thread的成员变量
/* What will be run. */
private Runnable target;
此时Thread中target就被设置为构造函数方法中传入的target,即为Runnable的实现。
最后再看一下run方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
当传入了target,就会执行target中的run方法。同样的,如果是通过继承Thread实例化,那就会执行继承是重写的run方法。
总得来说大致流程如下: