持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
首先,我是一个通过自学初入职场的努力程序猿,最近准备将原来知乎上的文章同步到CSDN。时至今日,我经历了一个人的寂寞,学习没有方向等很多问题。文章写的不是很好,但是对初学者自认为还是有点帮助的,大家有自学上的疑惑都可以留言或者私信交流!有求必应!
既然有想看Thread源码的需求,那么我相信大家对创建线程的三种方式早已经倒背如流,烂熟于心了。这边我就不再给大家介绍线程了,如果还不懂什么是线程的,请读者移步百度,谷歌或者知乎等其他平台进行了解。(若有时间,后面可能会更多线程相关的基础知识)
多做事少说话,多敲代码少放屁,好了我们准备冲了~~~~~
创建线程的三种方式
方式一:继承Thread类
方式二:实现Runnable接口
方式三:通过Callable和Future接口
在以上三种方式中,工作中最常用的是方式二,也就是实现Runnable接口。尽管我们知道了方式二是最常用的,但是喝水不忘挖井人。那么谁是挖井人呢?我们先来看看Runnable接口的源码。
什么Runnable里就一个空方法?不用干活就有钱赚?这或许是我们向往的生活吧。那么究竟是谁给Runnable接口打工了呢。这个时候我们就应该去看看方法二的线程是如何创建的,方式二是用了Thread类中带参数的构造方法,这个构造方法的形式参数是一个Runnable类型的实例对象。好了,破案成功,所有的原因都在Thread类中!接下来我们进入Thread类中一探究竟!
Thread源码解析
看完这个图相信大家有点想法了,接下来就通过上面的代码来看看方式一跟方式二创建线程的过程。
方式一:
实际上该方式就是调用start的事,前面的我只想讲讲整个程序运行的过程,有助于等下区分这几种方式的不同
注意:方式一采用继承的方式,所以父类Thread中的run方法是会被子类run()方法覆盖的,所以没走父类的run,而走了子类的run。
另外方式一的前三步实际上是没多大意义的,因为子类重写run方法后,父类的run方法是不会走的,也就是说前三步的target是在调用了父类的run方法后才有实际意义的,方拾二就有体现。
这个视频针对的是方式一,由于方式一直接重写了run方法,也就是说绿色方框中的if判断压根不会执行,所以自然也不会走Runable中的run()方法,而是执行你重写后的System.out.println("这是方式一");
方式二:
好像是有那么一点绕,调用来调用去到底是为什么?最后跑了一大圈,还是走的ThreadCreateDemo2中的run方法。
这里需要特别注意,方式一中的run方法是Thread类自己的,而方式二中的run方法是重写的Runnable接口中的run方法。Thread类和Runnable接口都有run方法,这里希望大家注意一下。 并且上个图的第六步是调用了Runnable中的run方法,由于图下载下来不清晰就不改了。
总之,继承Thread类重写的就是Thread类的run方法,实现Runnable接口重写的就是Runnable接口的run方法。
方式三:
由于第三中方式相对比较绕,所以相关文字写在下面,总体还是第二种方式。
首先,是创建了demo3对象,由于实现了Callable接口,因此可以说demo3是Callable类型的。
FutureTask类的构造方法需要传入一个Callable类型的对象,所以的demo3符合。
细节拉满!从源码中可以看出FutureTask继承了RunnableFuture接口,而RunnableFuture接口从源码中也可以看出它继承了Runnable接口。那是不是可以说FutureTask是Runnable类型了。
- ①:通过上面的分析,我们可以传入一个FutureTask的对象来创建一个线程
- ②:创建进程时,构造器走了init方法
- ③:在init方法中,将传入的FutureTask类型的对象(task对象)给了FutureTask类的成员变量target
- ④:线程调用start方法,start0方法唤醒Thread类中的run方法
- ⑤:通过第三步,这个时候调用FutureTask类中的run方法
- ⑥:通过查看FutureTask类中的run方法源码可以知道,它调用了Callable中的call方法