侧边栏壁纸
博主头像
翻斗

开始一件事最好是昨天,其次是现在

  • 累计撰写 44 篇文章
  • 累计创建 42 个标签
  • 累计收到 3 条评论

实现Runnable接口 vs 集成Thread类

翻斗
2020-04-19 / 0 评论 / 0 点赞 / 740 阅读 / 2,340 字

Baeldung 翻译系列之Java并发基础:

Java中的concurrent包
Java中的Synchronized关键字
Future介绍
ThreadLocal介绍
Java线程的生命周期
如何杀掉一个Java线程
Java中的线程池介绍
实现Runnable接口还是继承Thread类
Java中的wait和notify方法
Runnable vs Callable
wait和sleep的区别
Thread.join方法介绍
Java中使用锁对象
ThreadPoolTaskExecutor中的corePoolSize和maxPoolSize
Java中的异步编程

1. 介绍

这是一个很常见的问题:“我应该实现 Runnable 还是继承 Thread 类?”在实践中,哪种方法更有意义,我们将在这篇文章中进行探讨。

2. 使用Thread类

我们先等一一个 SimpleThread 类,它继承Thread

public class SimpleThread extends Thread {

    private String message;

    // standard logger, constructor

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

再看看我们如何使用这个类来运行一个线程

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

我们也可以通过使用 ExecutorService 来运行这个线程:

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

能够看出来要在一个线程里面运行一个单独的日志操作流程,还挺麻烦的。

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

3. 实现Runnable接口

现在看看我们实现 java.lang.Runnable 接口来做一个简单任务

class SimpleRunnable implements Runnable {
	
    private String message;
	
    // standard logger, constructor
    
    @Override
    public void run() {
        log.info(message);
    }
}

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

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

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

我们也可以使用一个ExecutorService:

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

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

从 Java 8 开始,任何公开单个抽象方法的接口都被视为函数式接口,这使得它可以作为 lambda 表达式的目标。

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

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

4. Runnable 还是 Thread ?

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

  • 当继承 Thread 类时,我们并未覆盖其任何方法。相反,我们覆盖的是 Runnable 的方法(Thread 恰好实现了它)。这明显违反了 IS-A Thread 原则。
  • 创建 Runnable 的实现并将其传递给 Thread 类使用的是组合而非继承 - 这更加灵活。
  • 扩展了 Thread 类后,我们不能再扩展任何其他类。
  • 从 Java 8 开始,Runnable 可以表示为 lambda 表达式。
    因此,在大多数情况下,实现 Runnable 接口比扩展 Thread 类更有意义。

5. 总结

这篇简短教程中,我们讨论了为什么实现Runnable接口通常来讲比继承Thread类更好的地方。

译者:补充一点,有些刚入门的朋友喜欢 实现一个Runnable之后,直接调用这个的run方法,比如上面的 new SimpleRunnable().run(),这样执行的内容,并不是在一个线程的上下文中执行的,没有任何意义,它只会在当前主线程中执行run这个方法。

0

评论区