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

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

一、背景

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

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

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

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

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

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

在这里插入图片描述

相关文章
|
5天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
44 17
|
16天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
18天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
18天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
18天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
42 3
|
18天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
104 2
|
26天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
48 6
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####