我们可以打开idea,按住ctrl将光标移至所查方法上,单击右键,即可查看这两个方法的源码:
1. start方法源码
我们从上至下分析一下:
这个threadStatus是一个int型的变量来表示线程是否开始,0为没有开始,非0为开始,因此当threadStatus不为0时,会抛出非法开始线程的异常.
group对象是用来记录已经开始的线程组的对象.设置started用来表示当前线程还没有开始,正准备要开始,因此初始化为false.
接下来,源码中使用了try-finally语句,在finally语句中又嵌套了try-catch语句.
第一个try中的语句十分关键:
其中调用了一个native方法(表示这个方法通过C++代码实现,JVM已经帮我们封装好了,我们无法查看其中具体的实现,直接调用即可)-->start0.
这个start0方法的描述中写道:如果这个线程使用一个被分离的Runnable对象,这个对象的run()方法就被调用了,否则这个方法直接返回.
接着,如果调用了对象的run方法,将started设置为true,进入finally语句;否则直接抛出一个Throwable对象;finally语句中通过判断started的值,如果started为false,将这个没有正常开启的线程加入group,catch Throwable对象,接下来的处理我们不过多介绍,着重分析成功开启线程的情况.
2. run方法源码
run这个方法看上去十分简单,上面有一个@Override注解,说明我们在创建线程应该要对这个方法进行重写,我们之前创建线程时也确实是这么做的.
在这个run方法中,判断了target对象是不是空,如果不是空的话,调用target对象的run方法.那么这个target对象是什么呢?通过作为隐式参数传递,实现了run方法,可以推断出它肯定是一个Thread类中定义的Runnable对象.我们如法炮制,ctrl右键单击点过去,揭开target的神秘面纱:
事实如我们所料,target这个对象表示哪个对象将被调用run方法.那么在new一个Thread对象时,如果我们什么都不传入,target引用的值默认为null,那么Thread类肯定有一个传入target对象的构造方法.
这里的确存在一个传入target的构造方法.这里面的init方法是用来初始化线程的一些基本属性,这里不做过多介绍.
3. 两个方法的联系和区别
3.1 是否需要重写
对于start方法,直接调用即可;而run方法需要被重写,才能被有意义地调用.
3.2 功能不同
start方法做的事比较多:线程的开启,以及开启成功或失败后的收尾操作;而run方法只注重于这个线程需要进行什么样的操作.
3.3 包含关系
start方法中会调用start0,start0方法中调用了Runnable对象的run方法;因此实际上是start方法在正常开启线程的过程中会调用run方法.
3.4 输出不同
start方法正常情况不会输出任何东西,根据源码我们发现即使是抛出了一个throwable对象,也并不会进行任何的打印操作.但是当线程已经开启时,调用start方法会抛出异常.
run方法则是可以进行输出.如果我们不重写run方法,也不会得到任何的输出.但是,如果我们在target对象传入时重写了run方法,在其中写了打印操作,就可以得到输出.