实现Runnable与继承Thread

2023/06/07

1. 概述

“我应该实现Runnable还是继承Thread类”?这是创建线程时一个很常见的问题。

在本文中,我们将看到哪种方法在实践中更有意义,以及为什么。

2. 使用Thread类

首先,我们定义一个继承Thread的SimpleThread类:

class SimpleThread extends Thread {
    private static final Logger log = LoggerFactory.getLogger(SimpleThread.class);

    private final String message;

    SimpleThread(String message) {
        this.message = message;
    }

    @Override
    public void run() {
        log.info(message);
    }
}

让我们看看如何运行这种类型的线程:

@Test
void givenAThread_whenRunIt_thenResult() throws Exception {
    Thread thread = new SimpleThread("SimpleThread executed using Thread");
    thread.start();
    thread.join();
}

我们还可以使用ExecutorService来执行线程:

private static ExecutorService executorService;

@BeforeAll
static void setup() {
    executorService = Executors.newCachedThreadPool();
}

@Test
void givenAThread_whenSubmitToES_thenResult() throws Exception {
    executorService.submit(new SimpleThread("SimpleThread executed using ExecutorService")).get();
}

@AfterAll
static void tearDown() {
    if (executorService != null && !executorService.isShutdown()) {
        executorService.shutdown();
    }
}

在一个单独的线程中运行单个日志操作需要相当多的代码。

另外,请注意SimpleThread现在不能继承任何其他类,因为Java不支持多重继承。

3. 实现Runnable接口

现在,让我们创建一个实现java.lang.Runnable接口的简单任务:

class SimpleRunnable implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(SimpleRunnable.class);

    private final String message;

    SimpleRunnable(String message) {
        this.message = message;
    }

    @Override
    public void run() {
        log.info(message);
    }
}

上面的SimpleRunnable只是一个我们希望在单独线程中运行的任务。

我们可以使用多种方法来运行它;其中之一是使用Thread类:

@Test
void givenARunnable_whenRunIt_thenResult() throws Exception {
    Thread thread = new Thread(new SimpleRunnable("SimpleRunnable executed using Thread"));
    thread.start();
    thread.join();
}

我们也可以使用ExecutorService:

@Test
void givenARunnable_whenSubmitToES_thenResult() throws Exception {
    executorService.submit(new SimpleRunnable("SimpleRunnable executed using ExecutorService")).get();
}

我们可以在这里阅读更多关于ExecutorService的内容。

由于我们现在正在实现一个接口,因此如果需要,我们可以自由地继承另一个父类。

从Java 8开始,任何只有单个抽象方法的接口都被视为函数接口,这使其成为有效的lambda表达式目标。

我们可以使用lambda表达式重写上面的Runnable代码

@Test
void givenARunnableLambda_whenSubmitToES_thenResult() throws Exception {
    executorService.submit(() -> log.info("Lambda runnable executed!!!")).get();
}

4. Runnable还是Thread?

简单地说,我们通常鼓励使用Runnable而不是Thread:

  • 在继承Thread类时,我们没有重写它的任何方法。相反,我们重写了Runnable的方法(Thread恰好实现了Runnable)。这显然违反了IS-A Thread原则。
  • 创建Runnable的实现并将其传递给Thread类使用组合而不是继承,这更灵活。
  • 在继承Thread类之后,我们不能继承任何其他类。
  • 从Java 8开始,Runnable可以表示为lambda表达式。

5. 总结

在这个快速教程中,我们看到了实现Runnable通常是比扩展Thread类更好的方法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章