`
frank-liu
  • 浏览: 1684021 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java创建线程的细节分析

阅读更多

前言

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

 

典型的线程创建过程

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

一、 实现Runnable接口

主要三个步骤:

1. 在类中间实现Runnable接口

2. 创建该对象实例

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

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

见代码如下:

public class ThreadCon implements Runnable
{
    public void run()
    {
        System.out.println("Entering the test thread...");
        try
        {
            Thread.sleep(5000);
            System.out.println("Thread executing...");
            Thread.sleep(5000);
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        ThreadCon con = new ThreadCon();
        Thread thread = new Thread(con);
        thread.start();
        System.out.println("main thread");
    }
}

 二、继承Thread类

主要就是两个步骤:

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

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

public class ThreadCon extends Thread
{
    public void run()
    {
        System.out.println("Entering the test thread...");
        try
        {
            Thread.sleep(5000);
            System.out.println("Thread executing...");
            Thread.sleep(5000);
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        ThreadCon thread = new ThreadCon();
        thread.start();
        System.out.println("main thread");
    }
}

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

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

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

    我们先来看第一个问题。

线程相关类结构

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

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

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

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

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

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

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

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

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

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

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

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

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

start方法的秘密

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

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    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 */
            }
        }
    }

    private native void start0();

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

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

main thread
Entering the test thread...
executing...

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

Entering the test thread...
Thread executing...
main thread

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

 两种创建线程方法的比较

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

总结

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

参考材料

http://openjdk.java.net/

http://stackoverflow.com/questions/13619958/running-thread-by-calling-start-and-run-what-is-the-difference?lq=1

http://stackoverflow.com/questions/541487/implements-runnable-vs-extends-thread

  • 大小: 8.8 KB
分享到:
评论

相关推荐

    java 多线程操作数据库

    本文将基于一个具体的Java多线程操作数据库的应用程序,深入探讨其背后的原理、实现细节以及潜在的挑战。 #### 核心知识点: 1. **多线程基础**:多线程是Java编程中的一个重要概念,允许程序同时执行多个任务。在...

    JAVA多线程练习题答案。

    在本文中,我们将对 JAVA 多线程练习题的答案进行详细的解释和分析。这些题目涵盖了 JAVA 多线程编程的基本概念和技术,包括线程的生命周期、线程同步、线程状态、线程优先级、线程安全等方面。 一、单项选择题 在...

    java多线程.pdf

    Java中可以通过两种方式创建线程: - **继承Thread类**:直接继承`java.lang.Thread`类并重写`run()`方法。 - **实现Runnable接口**:实现`Runnable`接口并覆盖`run()`方法,然后将其传递给`Thread`对象。 ##### 2...

    java 多线程设计模式 进程详解

    通过Thread类创建线程 使用Runable接口的线程 线程的生命周期 线程命名 访问线程 线程的启动、停止和连接 总结 第三章 同步技术 银行的例子 异步读取数据 一个进行同步操作的类 同步块 嵌套锁 死锁 返回到银行的...

    Java多线程设计模式_清晰完整PDF版 Java多线程设计模式源代码

    Java提供多种创建线程的方式,如继承Thread类、实现Runnable接口以及使用ExecutorService和Callable接口。每种方式有其适用场景和优缺点,例如,实现Runnable接口比继承Thread更灵活,因为可以避免类的单继承限制。 ...

    JAVA编写的多线程小弹球测试

    在“JAVA编写的多线程小弹球测试”项目中,开发者利用Java语言创建了一个生动有趣的多线程应用,即一个模拟小弹球运动的程序。这个程序的特点是弹球会随机出现、随机选择颜色,并且在碰到边界时能自动反弹,充分展示...

    java多线程作业.docx

    ### Java多线程知识点解析 #### 一、Java多线程概述 Java作为一种现代编程语言,内置了...通过以上分析,我们不仅了解了Java多线程的基本概念和技术细节,还通过具体的代码示例深入了解了多线程在实际编程中的应用。

    java多线程面试题59题集合

    - 实现Runnable接口:创建一个实现了Runnable接口的类,并实现run()方法,然后将实例对象传入Thread类的构造器创建线程。 - 实现Callable接口:创建一个实现了Callable接口的类,实现call()方法,通过FutureTask...

    JNI层创建的线程中回调java方法

    当我们需要在JNI层创建线程并从这些线程回调Java方法时,就需要对JNI的线程管理和Java方法调用有深入理解。 首先,我们需要了解Java线程和本地线程(即C/C++线程)的区别。Java线程是基于JVM(Java虚拟机)的,而...

    java多线程案例——未完成

    在案例中,可能涉及到上述的一个或多个知识点,比如创建线程、实现线程间通信、处理同步问题,或者利用并发工具类进行线程管理。由于案例是不完整的,具体的实现细节和问题还需要根据源代码来分析和补充。对于初学者...

    黑马程序员_张孝祥_Java多线程与并发库 视频+代码+资料

    2. **创建线程的方式**: - 继承`Thread`类:创建一个新类继承自`Thread`类,并重写`run()`方法。 - 实现`Runnable`接口:定义一个实现了`Runnable`接口的类,并实现其中的`run()`方法,然后将其作为参数传递给`...

    java多线程

    - 通过继承Thread类或实现Runnable接口创建线程; - 使用start()方法启动线程; - 掌握线程生命周期:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待(Waiting)、超时等待(Timed ...

    最好的java多线程电子书

    - 可以通过传递`ThreadGroup`实例作为参数来创建线程,并将其添加到特定的线程组中。 - **线程组的状态检查**: - 可以通过调用`activeCount()`方法来检测线程组中还有多少线程处于可运行状态。 #### 八、线程...

    实验八:Java多线程

    4. **Java实现多线程的两种方式**:通过继承`Thread`类或实现`Runnable`接口来创建线程。 5. **临界区、对象锁、互斥、同步等核心概念**:这些概念是实现线程安全的基础,其中临界区是指一段不能被多个线程同时访问...

    JAVA 线程实现数据库的主从同步更新

    在Java中,我们可以通过实现Runnable接口或者继承Thread类来创建线程。 在主从同步的场景下,我们可以创建两个线程:一个是主数据库的更新线程,负责处理写操作;另一个是从数据库的同步线程,用于定期检查主数据库...

    Java Socket学习---单线程阻塞

    此外,源码分析可以帮助我们更好地理解Socket通信的细节,例如异常处理、连接超时、资源管理等。通过阅读和实践这些示例代码,开发者可以掌握如何在Java中实现基本的网络服务和客户端应用。而标签中的"工具"可能指的...

    java 多线程数据库操作

    在Java中,可以通过实现`Runnable`接口或继承`Thread`类来创建线程。 在数据库操作中,多线程的应用主要有以下两个方面: 1. **并行查询**:当需要执行多个独立的数据库查询时,每个查询可以在单独的线程中执行,...

    创建线程的源代码资源

    在计算机编程中,多线程是并发执行任务的一种方式,...以上就是关于创建线程的基本知识,具体的实现细节会根据所使用的编程语言和版本有所不同。在实际开发中,应结合具体需求和语言特性选择合适的方式创建和管理线程。

    java实现多线程下载

    - **创建线程**:为每个部分创建一个线程,每个线程负责下载其对应的部分。 - **下载逻辑**:每个线程内部需要实现下载逻辑,可能涉及到HTTP请求,使用`java.net.URL`, `java.net.URLConnection`等类进行网络通信...

    Java多线程断点下载 带源码

    创建一个线程池或使用Java的`ExecutorService`来管理这些下载线程。每个线程负责下载文件的一个部分,然后将数据写入到本地文件的相应位置。 4. **断点续传机制**: 这是断点下载的核心。在下载过程中,我们需要...

Global site tag (gtag.js) - Google Analytics