转载:http://shmilyaw-hotmail-com.iteye.com/blog/1880902

前言

关于线程创建的问题,可以说是老生常谈了。在刚开始学习Thread的时候基本上都会接触到,用简单的一两句话就可以概括起来。一个是创建类实现Runnable接口,然后将该类的实例作为参数传入到Thread构造函数中。再调用Thread对象的start方法。还有一种是继承Thread类,覆写run方法。然后在该对象实例中调用start方法。那么,这两种方式在什么情况下适用呢?还有,既然我们要实现的类都要写run这个方法,为什么在构造的实例里要调用start方法呢?这多麻烦啊,还要绕一个弯,我直接调用run方法不行吗?这样岂不是更省事呢?对于这些问题,我会在本文中进行详细的分析和讨论。

典型的线程创建过程

这个过程可以说很简单,主要的两种方式如下:

一、 实现Runnable接口

主要三个步骤:

1. 在类中间实现Runnable接口

2. 创建该对象实例

3. 再新建一个Thread对象,将该对象作为参数传入构造函数

4. 调用Thread对象的start方法。

见代码如下:

  1. public class ThreadCon implements Runnable
  2. {
  3. public void run()
  4. {
  5. System.out.println("Entering the test thread...");
  6. try
  7. {
  8. Thread.sleep(5000);
  9. System.out.println("Thread executing...");
  10. Thread.sleep(5000);
  11. }
  12. catch(InterruptedException e)
  13. {
  14. e.printStackTrace();
  15. }
  16. }
  17. public static void main(String[] args)
  18. {
  19. ThreadCon con = new ThreadCon();
  20. Thread thread = new Thread(con);
  21. thread.start();
  22. System.out.println("main thread");
  23. }
  24. }

二、继承Thread类

主要就是两个步骤:

1. 集成Thread类,实现run方法。

2. 创建该类的实例,并调用start()方法。

  1. public class ThreadCon extends Thread
  2. {
  3. public void run()
  4. {
  5. System.out.println("Entering the test thread...");
  6. try
  7. {
  8. Thread.sleep(5000);
  9. System.out.println("Thread executing...");
  10. Thread.sleep(5000);
  11. }
  12. catch(InterruptedException e)
  13. {
  14. e.printStackTrace();
  15. }
  16. }
  17. public static void main(String[] args)
  18. {
  19. ThreadCon thread = new ThreadCon();
  20. thread.start();
  21. System.out.println("main thread");
  22. }
  23. }

看了这两部分代码之后,不禁有几个问题。

1. 这两种创建线程的方式,在Thread类里面是怎么定义的呢?

2. 我们继承类或者实现接口,都要覆写run()这个方法,为什么后面要调用start方法呢?他们之间有什么关系呢?

我们先来看第一个问题。

线程相关类结构

如果我们去仔细看Thread类和Runnable接口的实现的话,他们会有这么一个关系:

Thread类本身就继承了Runnable接口。

Runnable接口本身的定义很简单:

  1. public interface Runnable {
  2. /**
  3. * When an object implementing interface <code>Runnable</code> is used
  4. * to create a thread, starting the thread causes the object's
  5. * <code>run</code> method to be called in that separately executing
  6. * thread.
  7. * <p>
  8. * The general contract of the method <code>run</code> is that it may
  9. * take any action whatsoever.
  10. *
  11. * @see     java.lang.Thread#run()
  12. */
  13. public abstract void run();
  14. }

Thread类中间定义的run方法实现了Runnable接口的规范:

  1. @Override
  2. public void run() {
  3. if (target != null) {
  4. target.run();
  5. }
  6. }

上面这段代码里的target是一个Runnable的成员变量,在Thread里面的定义如下:

  1. /* What will be run. */
  2. private Runnable target;

另外,再看到Thread类中间有一个如下定义的构造函数:

  1. public Thread(Runnable target) {
  2. init(null, target, "Thread-" + nextThreadNum(), 0);
  3. }

我们就不难理解,我们通过构造一个实现Runnable接口的对象,将它作为参数传入Thread构造函数的原因了。这正好对应了我们第一种创建以及启动线程的过程。

对于第二种方式来说,当我们将一个类继承Thread的时候,通过覆写run方法,我们已经继承了Thread的定义,所以只要调用start方法就对应了我们第二种创建以及启动线程的过程。

写到这里的时候,我们会发现,还有一个遗漏了的地方。刚才只是讨论了我们要创建的对象和Runnable, Thread之间的关系。但是start方法到底怎么样,他们到底有什么关系呢?我们接下来很快就明白了。

start方法的秘密

我们来看看Thread里面start()方法的实现:

  1. /**
  2. * Causes this thread to begin execution; the Java Virtual Machine
  3. * calls the <code>run</code> method of this thread.
  4. * <p>
  5. * The result is that two threads are running concurrently: the
  6. * current thread (which returns from the call to the
  7. * <code>start</code> method) and the other thread (which executes its
  8. * <code>run</code> method).
  9. * <p>
  10. * It is never legal to start a thread more than once.
  11. * In particular, a thread may not be restarted once it has completed
  12. * execution.
  13. *
  14. * @exception  IllegalThreadStateException  if the thread was already
  15. *               started.
  16. * @see        #run()
  17. * @see        #stop()
  18. */
  19. public synchronized void start() {
  20. /**
  21. * This method is not invoked for the main method thread or "system"
  22. * group threads created/set up by the VM. Any new functionality added
  23. * to this method in the future may have to also be added to the VM.
  24. *
  25. * A zero status value corresponds to state "NEW".
  26. */
  27. if (threadStatus != 0)
  28. throw new IllegalThreadStateException();
  29. /* Notify the group that this thread is about to be started
  30. * so that it can be added to the group's list of threads
  31. * and the group's unstarted count can be decremented. */
  32. group.add(this);
  33. boolean started = false;
  34. try {
  35. start0();
  36. started = true;
  37. } finally {
  38. try {
  39. if (!started) {
  40. group.threadStartFailed(this);
  41. }
  42. } catch (Throwable ignore) {
  43. /* do nothing. If start0 threw a Throwable then
  44. it will be passed up the call stack */
  45. }
  46. }
  47. }
  48. private native void start0();

这部分的代码实际上并不长,这里主要的目的是调用了start0()这个方法。而start0这个方法在代码里的定义是一个private native的方法。这意味着什么呢?表示实际上这个start0方法是一个本地实现的方法。这个本地方法采用的不一定是java实现的,也可能是一个其他语言的实现,用来在JVM里面创建一个线程空间,再把run方法里面定义的指令加载到这个空间里。这样才能实现新建立一个线程的效果。所以,这也就是start方法能够创建线程的魔法之所在。

同时,这里也解释了为什么我们不能直接去调用run方法来期望它生成一个新的线程。如果我们直接调用run方法的话,它实际上就是在我们的同一个线程里面顺序执行的。在前面启动新线程的情况下,我们的代码执行结果会像如下的情况:

  1. main thread
  2. Entering the test thread...
  3. executing...

如果我们尝试将main函数里面的thread.start()改成thread.run(),我们会发现执行的结果如下:

  1. Entering the test thread...
  2. Thread executing...
  3. main thread

这种情况下run()方法和main函数其实是在同一个线程空间里执行。如果我们用一些profile工具进行分析的话,也会看到这样的场景。

两种创建线程方法的比较

通过继承的方式实现线程的方式用起来比较简单直接。但是从设计的角度来说,这个定义的类就需要和Thread方法有继承的关联关系。如果这个类本身又有其他的继承关系要处理呢?这个时候,这种方式就不是那么合适了。在Java本身的设计思路以及我们在面向对象设计思想的指导里,都不推荐优先考虑继承的关系。一个类可以实现多个接口,但是只能继承一个父类。所以说,采用实现Runnable接口的方式会是更加推荐的做法。

总结

对于线程的创建方式来说,实现接口的方法相对更加具有灵活性。因为这种方式不会和一个类建立固定的继承关系,可以更好的后续扩展一些。创建线程要调用start方法是因为start方法里面的一个特殊效果,它才能导致run方法的指令在新建的线程里执行。如果我们在建立实现runnable接口或者继承Thread对象的实例里直接调用run方法,这是不会产生新线程的。这也是为什么我们一定要调用start方法的原因。

Java创建线程的细节分析的更多相关文章

  1. Java并发编程:Java创建线程的三种方式

    目录 引言 创建线程的三种方式 一.继承Thread类 二.实现Runnable接口 三.使用Callable和Future创建线程 三种方式的对比 引言 在日常开发工作中,多线程开发可以说是必备技能 ...

  2. Java创建线程的三种主要方式

    Java创建线程的主要方式 一.继承Thread类创建 通过继承Thread并且重写其run(),run方法中即线程执行任务.创建后的子类通过调用 start() 方法即可执行线程方法. 通过继承Th ...

  3. 当阿里面试官问我:Java创建线程有几种方式?我就知道问题没那么简单

    这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家. 点赞再看,养成习惯~ 微信搜索[武哥聊编程],关注这个 Java 菜鸟. 昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的 ...

  4. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  5. 操作系统实现线程的几种模式 和 java创建线程的3个方式

    操作系统实现线程的几种模式 和 java创建线程的3个方式  这是两个概念 在操作系统中,线程可以实现在用户模式下,也可以实现在内核模式下,也可以两者结合实现. 1.实现线程的三种方式: (1)继承t ...

  6. Java创建线程的四种方式

    Java创建线程的四种方式 1.继承Thread类创建线程 定义Thread类的子类,并重写该类的run方法,run()方法的内容就是该线程执行的内容 创建Thread子类的实例,即创建了线程对象. ...

  7. java创建线程的多种方式

    java创建线程的四种方式 1.继承 Thread 类 通过继承 Thread 类,并重写它的 run 方法,我们就可以创建一个线程. 首先定义一个类来继承 Thread 类,重写 run 方法. 然 ...

  8. java创建线程的三种方法

    这里不会贴代码,只是将创建线程的三种方法做个笼统的介绍,再根据源码添加上自己的分析. 通过三种方法可以创建java线程: 1.继承Thread类. 2.实现Runnable接口. 3.实现Callab ...

  9. 【java】-- 线程池原理分析

    1.为什么要学习使用多线程? 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担. 线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致 ...

随机推荐

  1. bzoj 1066 : [SCOI2007]蜥蜴 网络流

    题目链接 给一个n*m的图, 里面每一个点代表一个石柱, 石柱有一个高度. 初始时有些石柱上面有蜥蜴, 蜥蜴可以跳到距离他曼哈顿距离小于等于d的任意一个石柱上,跳完后, 他原来所在的石柱高度会减一, ...

  2. ObjectiveC 文件操作二

    10,文件委托,以便操作文件.头部看起来像是这样. @interface MyFileManager : NSObject @property(strong)NSFileManager *fileMa ...

  3. 面试题之 query转为obj

    要注意处理编码后的字串  对于a=123要得到number形的值 function parseQueryString(url) { var obj = {}; var query = url.sear ...

  4. 走进Groovy (一)

    一直很喜欢脚本语言,但是一直在不大的公司工作,用得一直是“高大上”的JAVA语言,在真正的项目中,没什么机会用到脚本语言.这两年,又断断续续的用了2年的Ruby,再回头继续用JAVA,说实话,真感觉J ...

  5. delphi如何获得当前操作系统语言环境

    function GetWindowsLanguage: string; var WinLanguage: ..] of char; begin VerLanguageName(GetSystemDe ...

  6. Maven 添加Jetty

        <build>         <finalName>springmvc</finalName>         <plugins>       ...

  7. VS2010/MFC对话框:向导对话框的创建及显示

    向导对话框的创建及显示 本节将为大家演示如何创建向导对话框. 仍然以前面的“加法计算器”的例子为基础,在其中加入向导对话框,我们可以用它来说明加法计算器的使用方法,一步一步引导用户操作,这也是比较常见 ...

  8. 动画画圆的效果特效ios源码

    一款不错的支持动画画圆的效果特效源码,该效果实现了动画画圆,还可以扩展成画其他平面图形功能等,大家可以下载看看吧. //定义所需要画的图形  -(void)intiUIOfView  {      U ...

  9. 深入了解epoll (转)

    一. 介绍 Epoll 是一种高效的管理socket的模型,相对于select和poll来说具有更高的效率和易用性.传统的select以及poll的效率会因为 socket数量的线形递增而导致呈二次乃 ...

  10. Windows 配置JAVA的环境变量

    Java是由Sun公司开发的一种应用于分布式网络环境的程序设计语言,Java语言拥有跨平台的特性,它编译的程序能够运行在多种操作系统平台上,可以实现“一次编写,到处运行”的强大功能. 工具/原料 JD ...