继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

明明如月
关注TA
已关注
手记 59
粉丝 3857
获赞 1465

一、背景

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

题目1

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

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

题目2

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 为例:

/**
 * 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 源码:

public class TaskThread extends Thread {
//...
}

其实从本质上讲只有一种创建对象的方式,就是通过创建 Thread 或者子类的方式。
接下来让我们看下 Thread 类的注释:

/**
* There are two ways to create a new thread of execution. 
* One is to
* declare a class to be a subclass of <code>Thread</code>. This
* subclass should override the <code>run</code> method of class
* <code>Thread</code>. 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:
* <hr><blockquote><pre>
*     class PrimeThread extends Thread {
*         long minPrime;
*         PrimeThread(long minPrime) {
*             this.minPrime = minPrime;
*         }
*
*         public void run() {
*             // compute primes larger than minPrime
*             &nbsp;.&nbsp;.&nbsp;.
*         }
*     }
* </pre></blockquote><hr>
* <p>
* The following code would then create a thread and start it running:
* <blockquote><pre>
*     PrimeThread p = new PrimeThread(143);
*     p.start();
* </pre></blockquote>
* <p>
* The other way to create a thread is to declare a class that
* implements the <code>Runnable</code> interface. That class then
* implements the <code>run</code> method. An instance of the class can
* then be allocated, passed as an argument when creating
* <code>Thread</code>, and started. The same example in this other
* style looks like the following:
* <hr><blockquote><pre>
*     class PrimeRun implements Runnable {
*         long minPrime;
*         PrimeRun(long minPrime) {
*             this.minPrime = minPrime;
*         }
*
*         public void run() {
*             // compute primes larger than minPrime
*             &nbsp;.&nbsp;.&nbsp;.
*         }
*     }
* </pre></blockquote><hr>
* <p>
* The following code would then create a thread and start it running:
* <blockquote><pre>
*     PrimeRun p = new PrimeRun(143);
*     new Thread(p).start();
* </pre></blockquote>
* <p>
 */
public class Thread implements Runnable {

// 省略其他
}

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

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

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

2.2 运行结果是啥?

2.2.1 回顾

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

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

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run"));
    thread.start();
}

答案是打印: “Runnable run”

如果这么写(写法2):

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“。

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

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

target 部分:

/* 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 方法:

/**
 * If this thread was constructed using a separate
 * <code>Runnable</code> run object, then that
 * <code>Runnable</code> object's <code>run</code> method is called;
 * otherwise, this method does nothing and returns.
 * <p>
 * Subclasses of <code>Thread</code> 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 不就可以了吗:

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 源码。

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

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

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

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

本人慕课专栏: 解锁大厂思维:剖析《阿里巴巴 Java 开发手册》,感兴趣的同学可以了解下。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP