How to Start a Thread in Java – 如何在Java中启动一个线程

最后修改: 2018年 12月 9日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’re going to explore different ways to start a thread and execute parallel tasks.

在本教程中,我们将探索启动线程和执行并行任务的不同方法。

This is very useful, in particular when dealing with long or recurring operations that can’t run on the main thread, or where the UI interaction can’t be put on hold while waiting for the operation’s results.

这一点非常有用,特别是在处理无法在主线程上运行的长时间或重复性操作时,或者在等待操作结果时,UI交互不能被搁置。

To learn more about the details of threads, definitely read our tutorial about the Life Cycle of a Thread in Java.

要了解更多关于线程的细节,请务必阅读我们关于Java中线程的生命周期的教程

2. The Basics of Running a Thread

2.运行线程的基础知识

We can easily write some logic that runs in a parallel thread by using the Thread framework.

我们可以通过使用Thread框架轻松地编写一些在并行线程中运行的逻辑。

Let’s try a basic example, by extending the Thread class:

让我们尝试一个基本的例子,通过扩展Thread类。

public class NewThread extends Thread {
    public void run() {
        long startTime = System.currentTimeMillis();
        int i = 0;
        while (true) {
            System.out.println(this.getName() + ": New Thread is running..." + i++);
            try {
                //Wait for one sec so it doesn't print too fast
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ...
        }
    }
}

And now we write a second class to initialize and start our thread:

现在我们再写一个类来初始化和启动我们的线程。

public class SingleThreadExample {
    public static void main(String[] args) {
        NewThread t = new NewThread();
        t.start();
    }
}

We should call the start() method on threads in the NEW state (the equivalent of not started). Otherwise, Java will throw an instance of IllegalThreadStateException exception.

我们应该对处于start()状态(相当于未启动)的线程调用start()方法。否则,Java将抛出一个IllegalThreadStateException异常实例。

Now let’s assume we need to start multiple threads:

现在让我们假设我们需要启动多个线程。

public class MultipleThreadsExample {
    public static void main(String[] args) {
        NewThread t1 = new NewThread();
        t1.setName("MyThread-1");
        NewThread t2 = new NewThread();
        t2.setName("MyThread-2");
        t1.start();
        t2.start();
    }
}

Our code still looks quite simple and very similar to the examples we can find online.

我们的代码看起来仍然很简单,与我们在网上可以找到的例子非常相似。

Of course, this is far from production-ready code, where it’s of critical importance to manage resources in the correct way, to avoid too much context switching or too much memory usage.

当然,这与生产就绪的代码相去甚远,在那里,以正确的方式管理资源,避免过多的上下文切换或过多的内存使用,是至关重要的。

So, to get production-ready we now need to write additional boilerplate to deal with:

因此,为了让生产就绪,我们现在需要编写额外的模板来处理。

  • the consistent creation of new threads
  • the number of concurrent live threads
  • the threads deallocation: very important for daemon threads in order to avoid leaks

If we want to, we can write our own code for all these case scenarios and even some more, but why should we reinvent the wheel?

如果我们愿意,我们可以为所有这些情况写自己的代码,甚至更多,但为什么我们要重新发明轮子?

3. The ExecutorService Framework

3.ExecutorService框架

The ExecutorService implements the Thread Pool design pattern (also called a replicated worker or worker-crew model) and takes care of the thread management we mentioned above, plus it adds some very useful features like thread reusability and task queues.

ExecutorService实现了线程池设计模式(也称为复制的工作者或工作者-团队模式),并负责我们上面提到的线程管理,此外它还增加了一些非常有用的功能,如线程可重用性和任务队列。

Thread reusability, in particular, is very important: in a large-scale application, allocating and deallocating many thread objects creates a significant memory management overhead.

线程的可重用性尤其重要:在一个大规模的应用程序中,分配和取消分配许多线程对象会产生大量的内存管理开销。

With worker threads, we minimize the overhead caused by thread creation.

通过工作线程,我们将线程创建造成的开销降到最低。

To ease the pool configuration, ExecutorService comes with an easy constructor and some customization options, such as the type of queue, the minimum and the maximum number of threads and their naming convention.

为了简化池的配置,ExecutorService带有一个简单的构造器和一些自定义选项,如队列的类型、线程的最小和最大数量及其命名规则。

For more details about the ExecutorService, please read our Guide to the Java ExecutorService.

有关ExecutorService的更多细节,请阅读我们的Java ExecutorService指南

4. Starting a Task with Executors

4.用执行者启动一个任务

Thanks to this powerful framework, we can switch our mindset from starting threads to submitting tasks.

由于这个强大的框架,我们可以将我们的思维方式从启动线程转向提交任务。

Let’s look at how we can submit an asynchronous task to our executor:

让我们来看看如何向我们的执行器提交一个异步任务。

ExecutorService executor = Executors.newFixedThreadPool(10);
...
executor.submit(() -> {
    new Task();
});

There are two methods we can use: execute, which returns nothing, and submit, which returns a Future encapsulating the computation’s result.

我们有两个方法可以使用。execute,它不返回任何东西;submit,它返回一个封装了计算结果的Future

For more information about Futures, please read our Guide to java.util.concurrent.Future.

有关未来的更多信息,请阅读我们的java.util.concurrent.Future指南

5. Starting a Task with CompletableFutures

5.用CompletableFutures启动一个任务

To retrieve the final result from a Future object we can use the get method available in the object, but this would block the parent thread until the end of the computation.

为了从Future对象中检索最终结果,我们可以使用该对象中可用的get方法,但这将阻塞父线程,直到计算结束。

Alternatively, we could avoid the block by adding more logic to our task, but we have to increase the complexity of our code.

另外,我们可以通过在我们的任务中添加更多的逻辑来避免这个块,但我们必须增加我们代码的复杂性。

Java 1.8 introduced a new framework on top of the Future construct to better work with the computation’s result: the CompletableFuture.

Java 1.8在Future结构之上引入了一个新的框架,以更好地处理计算结果:CompletableFuture

CompletableFuture implements CompletableStage, which adds a vast selection of methods to attach callbacks and avoid all the plumbing needed to run operations on the result after it’s ready.

CompletableFuture实现了CompletableStage,它增加了大量的方法来附加回调,并避免在结果准备好后对其进行操作所需的所有管道。

The implementation to submit a task is a lot simpler:

提交任务的实现方式要简单得多。

CompletableFuture.supplyAsync(() -> "Hello");

supplyAsync takes a Supplier containing the code we want to execute asynchronously — in our case the lambda parameter.

supplyAsync需要一个Supplier,其中包含我们想要异步执行的代码–在我们的案例中是lambda参数。

The task is now implicitly submitted to the ForkJoinPool.commonPool(), or we can specify the Executor we prefer as a second parameter.

任务现在被隐式提交给ForkJoinPool.commonPool(),或者我们可以指定我们喜欢的Executor作为第二个参数。

To know more about CompletableFuture, please read our Guide To CompletableFuture.

要了解更多关于CompletableFuture的信息,请阅读我们的Guide To CompletableFuture

6. Running Delayed or Periodic Tasks

6.运行延迟或定期的任务

When working with complex web applications, we may need to run tasks at specific times, maybe regularly.

在处理复杂的网络应用程序时,我们可能需要在特定时间运行任务,也许是定期运行。

Java has few tools that can help us to run delayed or recurring operations:

Java很少有工具可以帮助我们运行延迟或重复性的操作。

  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1. Timer

6.1. 计时器

Timer is a facility to schedule tasks for future execution in a background thread.

Timer是一种设施,用于安排未来在后台线程中执行的任务。

Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals.

任务可以被安排为一次性执行,也可以在固定的时间间隔内重复执行。

Let’s see what the code looks if we want to run a task after one second of delay:

让我们看看如果我们想在延迟一秒后运行一个任务,代码是什么样子的。

TimerTask task = new TimerTask() {
    public void run() {
        System.out.println("Task performed on: " + new Date() + "n" 
          + "Thread's name: " + Thread.currentThread().getName());
    }
};
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);

Now let’s add a recurring schedule:

现在让我们添加一个经常性的时间表。

timer.scheduleAtFixedRate(repeatedTask, delay, period);

This time, the task will run after the delay specified and it’ll be recurrent after the period of time passed.

这一次,任务将在指定的延迟后运行,并且在时间段过后,它将会重复出现。

For more information, please read our guide to Java Timer.

欲了解更多信息,请阅读我们的Java Timer指南。

6.2. ScheduledThreadPoolExecutor

6.2.ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor has methods similar to the Timer class:

ScheduledThreadPoolExecutor具有与Timer类类似的方法。

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
ScheduledFuture<Object> resultFuture
  = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

To end our example, we use scheduleAtFixedRate() for recurring tasks:

为了结束我们的例子,我们使用scheduleAtFixedRate()来完成循环任务。

ScheduledFuture<Object> resultFuture
 = executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

The code above will execute a task after an initial delay of 100 milliseconds, and after that, it’ll execute the same task every 450 milliseconds.

上面的代码将在初始延迟100毫秒后执行一个任务,之后,每隔450毫秒执行一次相同的任务。

If the processor can’t finish processing the task in time before the next occurrence, the ScheduledExecutorService will wait until the current task is completed, before starting the next.

如果处理器不能在下一次发生之前及时完成任务的处理,ScheduledExecutorService将等待,直到当前任务完成,再开始下一个任务。

To avoid this waiting time, we can use scheduleWithFixedDelay(), which, as described by its name, guarantees a fixed length delay between iterations of the task.

为了避免这种等待时间,我们可以使用scheduleWithFixedDelay(),正如其名称所描述的,它保证了任务迭代之间的固定长度延迟。

For more details about ScheduledExecutorService, please read our Guide to the Java ExecutorService.

6.3. Which Tool Is Better?

6.3.哪种工具更好?

If we run the examples above, the computation’s result looks the same.

如果我们运行上面的例子,计算的结果看起来是一样的。

So, how do we choose the right tool?

那么,我们如何选择正确的工具

When a framework offers multiple choices, it’s important to understand the underlying technology to make an informed decision.

当一个框架提供多种选择时,了解底层技术以做出明智的决定很重要。

Let’s try to dive a bit deeper under the hood.

让我们试着在引擎盖下更深入地研究一下。

Timer:

Timer:

  • does not offer real-time guarantees: it schedules tasks using the Object.wait(long) method
  • there’s a single background thread, so tasks run sequentially and a long-running task can delay others
  • runtime exceptions thrown in a TimerTask would kill the only thread available, thus killing Timer

ScheduledThreadPoolExecutor:

ScheduledThreadPoolExecutor:

  • can be configured with any number of threads
  • can take advantage of all available CPU cores
  • catches runtime exceptions and lets us handle them if we want to (by overriding afterExecute method from ThreadPoolExecutor)
  • cancels the task that threw the exception, while letting others continue to run
  • relies on the OS scheduling system to keep track of time zones, delays, solar time, etc.
  • provides collaborative API if we need coordination between multiple tasks, like waiting for the completion of all tasks submitted
  • provides better API for management of the thread life cycle

The choice now is obvious, right?

现在的选择很明显,对吗?

7. Difference Between Future and ScheduledFuture

7.FutureScheduledFuture之间的区别

In our code examples, we can observe that ScheduledThreadPoolExecutor returns a specific type of Future: ScheduledFuture.

在我们的代码示例中,我们可以观察到ScheduledThreadPoolExecutor返回一种特定类型的FutureScheduledFuture

ScheduledFuture extends both Future and Delayed interfaces, thus inheriting the additional method getDelay that returns the remaining delay associated with the current task. It’s extended by RunnableScheduledFuture that adds a method to check if the task is periodic.

ScheduledFuture扩展了FutureDelayed接口,因此继承了额外的方法getDelay,该方法返回与当前任务相关的剩余延迟。它被RunnableScheduledFuture扩展,增加了一个方法来检查任务是否是定期的。

ScheduledThreadPoolExecutor implements all these constructs through the inner class ScheduledFutureTask and uses them to control the task life cycle.

ScheduledThreadPoolExecutor通过内类ScheduledFutureTask实现所有这些构造,并使用它们来控制任务生命周期。

8. Conclusions

8.8. 结论

In this tutorial, we experimented with the different frameworks available to start threads and run tasks in parallel.

在本教程中,我们试验了可用来启动线程和并行运行任务的不同框架。

Then, we went deeper into the differences between Timer and ScheduledThreadPoolExecutor.

然后,我们深入了解了TimerScheduledThreadPoolExecutor.之间的差异。

The source code for the article is available over on GitHub.

该文章的源代码可在GitHub上获得