A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 syb012 于 2015-11-10 17:59 编辑

复制代码
来源于阳哥视频第14天最后一个关于面试题的视频
  1. public class TreadTest {

  2.         public static void main(String[] args)
  3.         {
  4.                 new Thread(new Runnable()
  5.                 {
  6.                         public void run()
  7.                         {
  8.                                 System.out.println("Runnable...run");
  9.                         }
  10.                 })
  11.                 {
  12.                         public void run()
  13.                         {
  14.                                 System.out.println("subThread...run");
  15.                         }
  16.                 }.start();//以子类为主。为什么?。自己的理解:如果不以子类为主,这个语句将不通顺,父类对象后无法再加大括号。
  17.         }

  18. }
复制代码






感觉我自己的理解还是不太对,为什么要以子类为主呢?如果把它分开写可以吗?

这个代码还可以写成:
  1. class R implements Runnable
  2. {
  3.         public void run()
  4.         {
  5.                 System.out.println("Runnable...run");
  6.         }
  7. }

  8. public class ThreadTest2
  9. {

  10.         public static void main(String[] args)
  11.         {
  12.                 R r=new R();
  13.                 new Thread(r)
  14.                 {
  15.                         public void run()
  16.                         {
  17.                                 System.out.println("subThread...run");
  18.                         }
  19.                 }.start();
  20.         }        
  21. }
复制代码

这是,有一个问题,如果以子类为主,那么可以理解成在主函数中创建了一个对象,这个对象是线程子类的对象,并且在建立子类对象的时候进行了参数传递,传递了任务对象r。

但是子类对象只覆盖了父类的run函数,子类中并没有带有传递参数的构造函数,如果把匿名子类对象命名,会出现以下语句拆写困难
  1. new Thread(r)
  2.                 {
  3.                         public void run()
  4.                         {
  5.                                 System.out.println("subThread...run");
  6.                         }
  7.                 }.start();
复制代码

,比如,如果写成
  1. class R implements Runnable
  2. {
  3.         public void run()
  4.         {
  5.                 System.out.println("Runnable...run");
  6.         }
  7. }

  8. public class ThreadTest2
  9. {

  10.         public static void main(String[] args)
  11.         {
  12.                 R r=new R();               
  13.                 /*new Thread(r)
  14.                 {
  15.                         public void run()
  16.                         {
  17.                                 System.out.println("subThread...run");
  18.                         }
  19.                 }.start();*/
  20.                
  21.                 //Sub s=new Sub(r);//第一种:会报错,没办法传递参数r
  22.                 //Thread t= new Sub(r);//第二种,会报错,还是没办法传递参数r
  23.                 //Thread t=new Thread(r);//第三种,不会报错,但是与Thread的子类完全没有关系
  24.                 /*Sub b=new Sub();
  25.                 Thread t=(Thread)b;第四种:还是没办法传递参数*/
  26.                
  27.         }        
  28. }

  29. class Sub extends Thread
  30. {
  31.         public void run()
  32.         {
  33.                 System.out.println("subThread...run");
  34.         }
  35. }
复制代码

这么看来,好像只要写成匿名内部类的时候,才能在创建Thread子类对象的同时也传递了任务对象。

我似乎偏离了最初的思考问题的轨道。那个语句之所以运行结果打印的子类里的方法,是因为run方法覆盖了Thread原来的run方法,所以没办法运行到任务对象里的run方法。

可是,如果以任务为主,这句话就是先建立了Thread对象,那么后面的大括号就没有了意义。当以子类为主理解时才有意义。我好像又回到了最初的起点。
求大神棒我理清思路!!!!!!!

39 个回复

倒序浏览
回复 使用道具 举报
问老师吧...
回复 使用道具 举报

那你帮我圈个老师来吧
回复 使用道具 举报
过来看一下,好复习
回复 使用道具 举报
雨来 发表于 2015-11-11 11:23
过来看一下,好复习

那这个地方你是咋理解的啊?为什么这句代码没错呢?为什么这句代码以子类为主呢?
回复 使用道具 举报
海狮 中级黑马 2015-11-11 17:23:33
7#
什么叫以子类为主啊。。。。
回复 使用道具 举报
还没学到,帮不了你哟
回复 使用道具 举报
syb012 中级黑马 2015-11-11 20:01:04
9#
1379号监听员 发表于 2015-11-11 18:09
还没学到,帮不了你哟

回复 使用道具 举报
  1. /**
  2. * Thread--Runnable
  3. * @author Administrator
  4. *
  5. */
  6. public class ThreadTest {

  7.         public static void main(String[] args) {
  8.                 //我的看法是把程序拆解来看
  9.                 /*
  10.                 new Thread(new Runnable(){
  11.                         public void run(){
  12.                                
  13.                         }
  14.                 }){
  15.                         public void run(){
  16.                                
  17.                         }
  18.                 };*/
  19.                 //发现没,这样写是不会报错的
  20.                 /**
  21.                  * 那么说明什么呢?
  22.                  * 其实和下面:
  23.                  */
  24.                 /*
  25.                 new Runnable(){
  26.                         public void run(){}
  27.                 };*/
  28.                 //这是Runnable实现是一个道理的
  29.                 /*
  30.                  * 下面
  31.                  * */
  32.                 new Thread(){
  33.                         //public void run(){}//这里是继承Thread类
  34.                 };
  35.                 /**
  36.                  * 不知道还记得不,创建线程的两种方式:一种是继承Thread类
  37.                  * 一种是实现Runnable接口
  38.                  * 而下面:
  39.                  */
  40.                 new Thread(new Runnable(){
  41.                         public void run(){
  42.                                 System.out.println(1);
  43.                         }
  44.                 }){
  45.                         public void run(){
  46.                                 System.out.println(2);
  47.                         }
  48.                 }.start();
  49.                 /*
  50.                  * 其实容易混淆的是这个部分:Java虽然没有多继承,但是有多实现代替了
  51.                  * 而放到这个题目上是:他们一个继承了Thread类,一个实现了Runnable接口
  52.                  * 方式不同,但是同为匿名子类
  53.                  * 重点:始终,我们只调用了一次,start()方法
  54.                  * 现在的问题变成了:这个start()方法是谁的?
  55.                  * 仔细看不难发现,程序开始是哪个实例在执行?呼之既出了,Thread
  56.                  * */
  57.                
  58.         }
  59. }
复制代码
回复 使用道具 举报 1 0


分析的很对。  

但是这么写代码。   
还真是操蛋!
回复 使用道具 举报
冰霜之卅 发表于 2015-11-11 21:37
分析的很对。  

但是这么写代码。   

我喜欢比较通俗点的啦,您觉得哪些地方需要改正的呢,因为自己也在写代码,就没分开写了,勿怪咯{:2_36:}
回复 使用道具 举报
syb012 中级黑马 2015-11-12 19:21:42
13#
本帖最后由 syb012 于 2015-11-12 20:16 编辑

最后一句,程序是哪个实例调用了start方法。那么这个程序有多少个实例呢?

一个是实现Runnable接口的任务实例,但它不能之间调用start方法,它必须作为Thread的构造函数参数生成新
Thread的实例才能调用start方法。记为实例①

另一个实例是继承Thread类的匿名子类对象,它也可以调用start方法。记为实例②

你说的没有错,只能有一个实例调用了start方法,从运行结果可以得出调用start方法的是实例②。
既Thread类的匿名子类对象调用了start方法,而不是实现Runnable接口的任务对象间接调用。

可是为什么呢?我知道结果却不知道原因,有点不甘心。视频里毕老师说必须是以子类为主,我可以验证,可就是理解不了。为什么当“Thread类的匿名子类对象”和“以实现Runnable接口的任务对象作为参数的Thread实例”同时存在时,是前者而不是后者调用start方法?

又把那段视频看了一遍,毕老师说必须以子类为主是因为子类的run方法覆盖了父类的。我不太喜欢这个原因,因为任务对象作为参数传给Thread的构造函数生成的新的对象如果调用start,再执行run方法应该和Thread的匿名子类没有关系才是。因为那个子类里没有带参数的构造函数,所以不可能是把任务对象作为参数传给了Thread的匿名子类。既然毫无关系,不是多态,又怎么会覆盖呢?
回复 使用道具 举报
syb012 发表于 2015-11-12 19:21
最后一句,程序是哪个实例调用了start方法。那么这个程序有多少个实例呢?

一个是实现Runnable接口的任务 ...

首先,毕老师从多态的角度讲解很专业,毕老师杠杠滴!
我们知道,Thread类实现了Runnable接口.
这里,通过查阅API可以发现(毕老师也查看过),从栈堆的角度去分析:
先看这个都知道的:
String name="张三";//一个对象
name="李四";//另一个对象
这个怎么理解大家都知道,就是栈内存中变量name在堆中的地址值指向发生了改变...
!--new Thread(new Runnable(){public void run(){}}){public void run(){}};
不偷懒,把它写全:
Thread thread=new Thread(new Runnable(){public void run(){}}){public void run(){}};
把他们分开:(当然)
因为有两个new,必然在堆内存中产生了两个对象--实例;
在理解的过程中我们可以这么想:
首先:thread指向了new Thread(new Runnable(){});
然后,thread改成了指向Thread的子类对象new Thread(){public void run(){}};
//他们都是可以独立运行的,不同的是,至始至终,栈内存中只有一个thread,所以
就是"李四"了
也就是说:最终只有一个结果:那就是Thread子类被thread指向
那么:
//伪代码
new Thread(new Runnable(){public void run(){}}){public void run(){}}.start();
等同于thread.start();
等同于new Thread(){public void run(){}}.start();
回复 使用道具 举报
syb012 中级黑马 2015-11-12 23:13:07
15#
萧未然 发表于 2015-11-12 21:49
首先,毕老师从多态的角度讲解很专业,毕老师杠杠滴!
我们知道,Thread类实现了Runnable接口.
这里,通 ...

就是说一个虚拟的指针被赋予了“以实现Runnable接口的任务对象作为参数的Thread实例”的地址值后,又被“Thread的匿名子类对象”的地址值,所以最后是用后者的地址值调用了start方法

我感觉我现在能够理解为什么最后运行是匿名子类里run方法了。谢谢
回复 使用道具 举报
syb012 中级黑马 2015-11-12 23:17:42
16#
萧未然 发表于 2015-11-12 21:49
首先,毕老师从多态的角度讲解很专业,毕老师杠杠滴!
我们知道,Thread类实现了Runnable接口.
这里,通 ...

感觉还是有点怪,两个new是没错,但当中的第二个new是用来创建实现Runnable接口的任务对象的;

第一个new创建的是“以实现Runnable接口的任务对象作为参数的Thread实例”呢?
还是创建“Thread的匿名子类对象”呢?
回复 使用道具 举报
syb012 发表于 2015-11-12 23:17
感觉还是有点怪,两个new是没错,但当中的第二个new是用来创建实现Runnable接口的任务对象的;

第一个ne ...

第一个new当然是用来创建Thread对象的,有没有发现
new Thread().start();这样也是不会报错的,这主要是因为只有Thread才有
start()方法,而start()的功能是什么?
start()就是负责线程调用run()方法执行代码,仔细分析,就会发现,问题都出现在run()方法内部,它是怎么调用资源的?
!--首先,看看API中对Thread类中run方法的描述:
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。Thread 的子类应该重写该方法。
!--明确一点:Thread类实现了Runnable,那么它本身必然会有一个不返回任何操作Run()方法
,所以new Thread().start();没有任何数据显示
!--这里可以知道:原来后面无论是带Runnable构造参数也好,还是Thread子类也罢,都只不过是对一个线程中唯一的run()方法进行不断的复写而已,当线程还没有开启前,就是不断的更新run方法:下面
int i=0;
i=1;
i=2;
----------如果最后对i进行操作:int j=i;结果当然是2;
!--所以run方法不断更新,先是Ruunable匿名子类中的run()方法内容,然后又更行了,Tread匿名之类中的run()方法内容,我们可以这么理解,线程中run()方法一直都在,只是它的内容在不断的变化,而最终的结果,就是start()调用run()方法后的操作数据....
!--补充一下Thread带Runnable子类的构造函数:
       因为资源这个事物不确定,为了提高程序扩展性,于是就对外提供了一个Runnable接口,让线程能够动态的运行,而运行的关键就是run方法代码,也就是说,run()方法也只不过是,把需要操作的数据进行一个封装然后定义一个run标记而已,而线程运行函数strat()就是专门调用run标记里面的内容加载到线程当中
回复 使用道具 举报
要我说呀,这就是一个标记型的接口,实现了这个runnable接口,就等于有了多线程的功能。子类的run方法确实覆盖了父类的,而且new出来的线程就是子类的线程,调用start方法时当然也是调用子类的方法。
回复 使用道具 举报
syb012 中级黑马 2015-11-14 12:56:49
19#
李永佳 发表于 2015-11-13 12:21
要我说呀,这就是一个标记型的接口,实现了这个runnable接口,就等于有了多线程的功能。子类的run方法确实 ...

感觉你说的比较有道理,感觉楼上说的那个好混乱,有点前后不通的感觉
回复 使用道具 举报
syb012 中级黑马 2015-11-14 13:00:57
20#
如果能搞清楚对象在堆内存是怎么变化的,最后堆内存的对象是什么样子的,问题应该就能明了了
回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马