你真得知道Java 中有几种创建线程的方式吗?

2021-08-31 11:23:29 浏览数 (2)

一、背景

本文给出两个简单却很有意思的线程相关的题目

题目1

Java 中有几种创建线程的方式?

如果面试中遇到这个问题,估计很多人会非常开心,然而网上的诸多答案真的对吗?

题目2

代码语言:javascript复制
public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run")) {
        @Override
        public void run() {
            System.out.println("Thread run");
        }
    };
    thread.start();
}

请问运行后输出的结果是啥?

拿到这个问题有些同学可能会懵掉几秒钟,什么鬼…

二、分析

2.1 有几种创建形成的方式

不知道大家想过没有,本质上 JDK 8 中提供了几种创建线程的方式?

可能很多人会讲可以先创建 Runnable 当做参数传给 Thread ,可以写匿名内部类,可以编写 Thread 的子类,可以通过线程池等等。

其实线程池的 Worker 内部还是通过 Thread 执行的,而Worker 中的线程是通过 ThreadFactory 创建,ThreadFactory 最终还是通过构造 Thread 或者 Thread 子类的方式创建线程的。

org.apache.tomcat.util.threads.TaskThreadFactory 为例:

代码语言:javascript复制
/**
 * Simple task thread factory to use to create threads for an executor
 * implementation.
 */
public class TaskThreadFactory implements ThreadFactory {

    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    private final boolean daemon;
    private final int threadPriority;

    public TaskThreadFactory(String namePrefix, boolean daemon, int priority) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        this.namePrefix = namePrefix;
        this.daemon = daemon;
        this.threadPriority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        TaskThread t = new TaskThread(group, r, namePrefix   threadNumber.getAndIncrement());
        t.setDaemon(daemon);
        t.setPriority(threadPriority);

        // Set the context class loader of newly created threads to be the class
        // loader that loaded this factory. This avoids retaining references to
        // web application class loaders and similar.
        if (Constants.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    t, getClass().getClassLoader());
            AccessController.doPrivileged(pa);
        } else {
            t.setContextClassLoader(getClass().getClassLoader());
        }

        return t;
    }
}

TaskThread 源码:

代码语言:javascript复制
public class TaskThread extends Thread {
//...
}

其实从本质上讲只有一种创建对象的方式,就是通过创建 Thread 或者子类的方式。

接下来让我们看下 Thread 类的注释:

代码语言:javascript复制
/**
* There are two ways to create a new thread of execution. 
* One is to
* declare a class to be a subclass of Thread. This
* subclass should override the run method of class
* Thread. An instance of the subclass can then be
* allocated and started. For example, a thread that computes primes
* larger than a stated value could be written as follows:
* *     class PrimeThread extends Thread {
*         long minPrime;
*         PrimeThread(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:
* *     PrimeThread p = new PrimeThread(143);
*     p.start();
* 
* 
* The other way to create a thread is to declare a class that
* implements the Runnable interface. That class then
* implements the run method. An instance of the class can
* then be allocated, passed as an argument when creating
* Thread, and started. The same example in this other
* style looks like the following:
* *     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();
* 
* 
 */
public class Thread implements Runnable {

// 省略其他
}

通过注释我们可以看出有两种创建执行线程的方式:

  • 继承 Thread 并且重写 run 方法。
  • 实现 Runnable 接口实现 run 方法,并作为参数来创建 Thread。

如果是从这个层面上讲,有两种创建 Thread 的方式,其他方式都是这两种方式的变种。

2.2 运行结果是啥?

2.2.1 回顾

可能很多同学看到这里会有些懵。

如果下面这种写法(写法1):

代码语言:javascript复制
public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run"));
    thread.start();
}

答案是打印: “Runnable run”

如果这么写(写法2):

代码语言:javascript复制
public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println("Thread run");
        }
    };
    thread.start();
}

答案是打印“Thread run”

一起写,这个操作有点风骚…

2.2.2 莫慌

我们可以确定的是 thread.start 调用的是 run 方法,既然这里重写了 run 方法,肯定调用的是咱们重写的 run 方法。

因此答案是 "Thread run“。

为了更好地搞清楚这个问题,咱么看下源码:

代码语言:javascript复制
public Thread(Runnable target) {
    init(null, target, "Thread-"   nextThreadNum(), 0);
}

target 部分:

代码语言:javascript复制
/* What will be run. */
private Runnable target;

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {

    // ....
    this.target = target;

}

我们再看下默认的 run 方法:

代码语言:javascript复制
/**
 * If this thread was constructed using a separate
 * Runnable run object, then that
 * Runnable object's run method is called;
 * otherwise, this method does nothing and returns.
 * 
 * Subclasses of Thread should override this method.
 *
 * @see     #start()
 * @see     #stop()
 * @see     #Thread(ThreadGroup, Runnable, String)
 */
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

注释说的很清楚,通过构造方法传入 Runnable ,则调用 Runnable的 run 方法,否则啥都不干。

因此这就是为什么写法1 的结果是:“Runnable run”。

如果咱们重写了 run 方法,默认 target 的就失效了,因此结果就是"Thread run“。

如果我想先执行 Runnbale 的 run 方法再执行咱们的打印"Thread run“咋办

既然上面了解到父类的默认行为是执行构造函数中 Runnbale 对象的 run 方法,咱么先调用 super.run 不就可以了吗:

代码语言:javascript复制
public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run")) {
        @Override
        public void run() {
            super.run();
            System.out.println("Thread run");
        }
    };
    thread.start();
}

输出结果:

Runnable run Thread run

其实这个题目重点考察大家有没有认真看过源码,有没有真正理解多态的含义,是否都线程基础有一定的了解。

三、总结

这个问题本质上很简单,实际上很多人第一反应会懵掉,是因为很少主动去看 Thread 源码。

学习和工作的时候更多地是学会用,而不是多看源码,了解原理。

通过这个简单的问题,希望大家学习和工作之余可以养成查看源码的习惯,多动手练习,多思考几个为什么。

希望大家读书时,尤其是看博客文章时,不要想当然,多思考下问题的本质。

如果你觉得本文对你有帮助,欢迎点赞评论,你的支持和鼓励是我创作的最大动力。

0 人点赞