Thread类源码解读(1)——如何建立和启动线程

前言

系列文章目录 html

谈到线程同步与通讯,线程自己的概念是绕不开的,而进程和线程的概念已是老生常谈的话题了,一些基本的概念本文就再也不讨论了,本篇仅仅致力于经过源码,了解线程的构造与启动,从而更深刻的了解线程。java

本文源码基于jdk1.8 。面试

阅读完本文,你应当有能力回答如下常见面试题:segmentfault

  1. 建立线程有哪几种方式?
  2. 如何启动一个线程?
  3. 线程的run方法和start方法有什么区别?

Runnale接口

咱们看Thread类的定义知道,它实现了Runable接口api

public class Thread implements Runnable {
    ...
}

Runnable接口的定义以下:多线程

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

它只有一个抽象方法run。同时,该接口还被@FunctionalInterface注解标注,说明它是一个函数式接口(@FunctionalInterface是java 1.8版本以后引入的)。这意味着咱们可使用Lambda表达式来建立Runnable接口的实例,这个咱们到后面再举例。并发

线程建立

在java中,建立一个线程,有且仅有一种方式: oracle

建立一个Thread类实例,并调用它的start方法。 ide

这写在了java语言规范中(参见The Java Language Specification, Java SE 8 Edition, P659,chapter17):函数

Threads are represented by the Thread class. The only way for a user to create a thread is to create an object of this class; each thread is associated with such an object. A thread will start when the start() method is invoked on the corresponding Thread object.

构造函数

要建立一个Thread类的实例天然要经过构造函数,Thread的public构造函数有8个之多,可是他们本质上都调用了同一个init函数:

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
    init(null, null, name, 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
    init(group, target, name, stackSize);
}

可见,这八个public类型的构造函数只不过是给init的方法的四个参数分别赋不一样的值, 这四个参数分别是:

  • ThreadGroup g(线程组)
  • Runnable target (Runnable 对象)
  • String name (线程的名字)
  • long stackSize (为线程分配的栈的大小,若为0则表示忽略这个参数)

而init方法又调用了另外一个init方法,设置了AccessController,以及inheritThreadLocals参数:

/**
 * Initializes a Thread with the current AccessControlContext.
 * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null, true);
}

//上面那个init方法最终调用了下面这个方法:

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
    ...
}

init方法中有一些关于线程组和访问控制上下文的设置,这里咱们暂时就不深刻讨论了。

因此综上来看,咱们最经常使用的也就两个参数:

  • Runnable target (Runnable 对象)
  • String name (线程的名字)

而对于线程的名字,其默认值为"Thread-" + nextThreadNum(), nextThreadNum方法又是什么呢:

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

可见,它就是一个简单的递增计数器,因此若是建立线程时没有指定线程名,那线程名就会是:
Thread-0, Thread-1, Thread-2, Thread-3, ...

至此,咱们看到,虽然Thread类的构造函数有这么多,但对咱们来讲真正重要的参数只有一个:

Runnable target (Runnable 对象)

因此建立一个线程实例最重要的是要传入一个Runnable类型对象。

既然是Runnable类型,那么这个target必然是实现了Runnable接口的,也就是说该对象必定覆写了run方法。

咱们知道,Thread类自己也实现了Runnable接口,因此它必然也覆写了run方法,咱们先来看看它的run方法:

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

能够看到,这个run方法仅仅是调用了target对象的run方法,若是咱们在线程构造时没有传入target(例如调用了无参构造函数),那么这个run方法就什么也不会作。

启动线程

线程对象建立完了以后,接下来就是启动一个线程,在java中,启动一个线程必须调用线程的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()

这个方法本质是调用了native的start0()方法,可是它的注释部分说明一些很重要的信息:

这个方法使得线程开始执行,并由JVM来执行这个线程的run方法,结果就是有两个线程在并发执行,一个是当前线程,也就是调用了Thread#start方法的线程,另外一个线程就是当前thread对象表明的线程,它执行了run方法。

也就是说,这个Thread类实例表明的线程最终会执行它的run方法,而上面的分析中咱们知道,它的run作的事就是调用Runnable对象的run方法,若是Runnable对象为null, 就啥也不作:

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

有的同窗就要问了,绕了一大圈,忙了大半天,最后不就是为了执行target对象的run方法吗?为何咱们不直接调用target的run方法?这一层层的调用到底是为了啥? 答案是:

为了使用多线程 !

咱们知道,Thread类从定义上看就是个普通的java类,是什么魔法让它从一个普通的java类晋升为一个能够表明线程的类呢?是native方法!

若是咱们直接调用target对象的run方法,或者Thread类的run方法,那就是一个普通调用,由于run方法就是普普统统的类方法,与咱们平时调用的其余类方法没有什么不一样,这并不会产生多线程。

可是,若是咱们调用了start方法,因为它内部使用了native方法来启动线程,它将致使一个新的线程被建立出来, 而咱们的Thread实例, 就表明了这个新建立出来的线程, 而且由这个新建立出来的线程来执行Thread实例的run方法。

实战

说了这么多理论的东西,下面让咱们经过一个实战来加深理解。java官方文档给咱们提供了两种建立线程的方法.

方法1:继承Thread类,覆写run方法

首先咱们自定义一个继承自Thread的类,并覆写run方法:

public class CustomizedThread extends Thread {
    public void run() {
        System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我是定义在CustomizedThread类中的run方法。");
    }
}

而后咱们建立类的实例,并调用start方法启动这个线程:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我在main方法里");
        CustomizedThread myThread = new CustomizedThread();
        myThread.start();
    }
}

执行结果:

[main线程]: 我在main方法里
[Thread-0线程]: 我是定义在CustomizedThread类中的run方法。

可见,这里有两个线程,一个是main线程,它执行了main方法,一个是Thread-0线程,它是咱们自定义的线程,它执行了run方法。

若是咱们不经过start方法来运行线程会有什么不一样呢:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我在main方法里");
        CustomizedThread myThread = new CustomizedThread();
        //myThread.start();
        myThread.run();
    }
}

这里咱们直接调用自定义线程的run方法,看看结果有什么不一样:

[main线程]: 我在main方法里
[main线程]: 我是定义在CustomizedThread类中的run方法。

可见,此次只有一个main线程,由main线程执行了咱们自定义线程类的run方法,并无新的线程产生。 其实这个时候,CustomizedThread的run方法就是一个普普统统的类的普普统统的方法,与咱们平时定义的方法并无什么特别之处。

有的同窗要问了,上面不是说建立一个线程最重要的是传入一个Runnable对象吗? 我没有看到Runnable对象啊? 别急,咱们来分析一下:

首先,咱们的CustomizedThread继承自Thread类,则咱们会调用父类的无参构造函数:

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

这个构造函数中,target对象为null;

而后,咱们使用了myThread.start(),由于咱们在子类中没有定义start方法,因此,这个方法来自父类,而Thread类的start方法的做用咱们已经讲过,它将新建一个线程,并调用它的run方法,这个新建的线程的抽象表明就是咱们的CustomizedThread,因此它的(CustomizedThread的)run方法将会被调用。

那么,若是咱们的子类没有覆写run方法呢?,那天然是继承Thread类本身的run方法了:

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

而Thread类的run方法调用的又是target对象的run方法,而target对象如今为null, 因此这个方法啥也不作。

因此到这里咱们就很清晰了,建立一个线程最重要的是定义一个run方法,这个run方法要么经过继承Thread类的子类覆写,要么经过直接构造Thread类时传入一个Runnable的target对象。不管它由子类覆写提供仍是由target对象提供,start方法最终都会新建一个线程来执行这个run方法。

方法2:经过Runnable接口建立线程类

咱们先来看官方的例子:

class PrimeRun implements Runnable {
     long minPrime;
     PrimeRun(long minPrime) {
         this.minPrime = minPrime;
     }

     public void run() {
         // compute primes larger than minPrime
          . . .
     }
}
//The following code would then create a thread and start it running:

 PrimeRun p = new PrimeRun(143);
 new Thread(p).start();

这个例子中首先定义了一个PrimeRun类实现了Runnable接口,接着实例化出一个对象p,并将这个对象做为参数传递给Thread类的构造方法。

这种方法看上去好像复杂了好多,但其实就是经过新建Thread类的对象来建立线程。它本质上就是传递一个Runnable对象给Thread的构造函数,因此咱们彻底能够用匿名类,又由于Runnable是一个函数接口,因此上面的代码彻底能够被简写,咱们来看一个例子:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我在main方法里");
        Thread myThread = new Thread(() -> System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我是传递给Thread类的Runnable对象的run方法"));
        myThread.start();
    }
}

代码输出:

[main线程]: 我在main方法里
[Thread-0线程]: 我是传递给Thread类的Runnable对象的run方法

这里,myThread是咱们new出来的Thread类的实例,咱们调用了Thread类的构造函数:

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

传入了一个Runnable对象,这个Runnable对象由lambda表达式表示。咱们最后调用了 myThread.start()来启动这个线程,经过上一节的分析咱们知道,start方法会调用run方法,而thread类的run方法最终会调用target对象的run方法,而target对象的run方法就是咱们传进来的lambda表达式。上面这个例子其实等效于下面这种写法:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我在main方法里");
        Thread myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[" + Thread.currentThread().getName() + "线程]: " + "我是传递给Thread类的Runnable对象的run方法");
            }
        });
        myThread.start();
    }
}

可见函数式接口和lambda表达式使咱们的书写变得简洁多了。

总结

在java中,建立一个线程,有且仅有一种方式:

建立一个Thread类实例,并调用它的start方法。

建立一个Thread类的实例最重要的是定义一个run方法,这个run方法说明了这个线程具体要作什么事情。有两种方式定义一个run方法:

  1. 继承Thread类,覆写run方法
  2. 实现Runnale接口,将它做为target参数传递给Thread类构造函数

启动一个线程必定要调用该线程的start方法,不然,并不会建立出新的线程来。

(完)

查看更多系列文章:系列文章目录

相关文章
相关标签/搜索