Java多线程:实现Runnable接口如何用于线程?

5

我知道在Java中,如果你想要使用多线程,可以通过扩展Thread类或实现Runnable接口来实现。但是为什么必须要实现一个接口来使Java实现线程呢?Runnable接口的重要性是什么,它是如何使Java线程工作的?Java的接口继承自哪里?

5个回答

11
< p > Runnable 接口的唯一特殊之处在于它是作为 Thread 构造函数的参数而存在。它只是一个普通的接口。

与大多数接口一样,重点是你要遵循某种契约:你同意将想要运行的代码放在 Runnable#run() 实现中,Thread 同意在另一个线程中运行该代码(当你使用 Thread 创建并启动它时)。

实际上,是 Thread 在执行"多线程"任务(即与本地系统交互)。Runnable 的实现仅是你要告诉 Thread 要运行的代码所在的位置。

事实上,你可以实现一个 Runnable 并运行它,而不必在单独的线程中运行:

Runnable someCode = new Runnable() {
    public void run() {
       System.out.println("I'm a runnable");
    }
};
someCode.run();

所以Runnable本身与多线程无关,它只是一个标准接口,用于将一块代码封装在对象中时扩展。


4
+1 意味着,Runnable 只是定义了一个接口,以便线程知道“调用哪个方法”/“从哪里开始”... 有些人会不小心调用 theRunnable.run() 并期望它能自动启动线程:但实际上并不会。 - user166390
这就是我正在寻找的信息。因此,线程知道某个可运行的方法存在,我们只需提供它 - 因此需要接口。 - stackoverflow
@stackoverflow:没错,接口编程就是遵循契约的编程。 - Mark Peters
4
话虽如此,实现并发编程最常见(也是首选)的方法之一是_尽可能避免_直接操作线程,而是将“Runnable”传递给“Executor”。 - Louis Wasserman
@Louis:非常正确,但它们也没有什么特别的地方——它们(不可避免地?)在底层使用Thread - Mark Peters

2
但是为什么你必须要实现一个接口来让Java线程工作呢?
其实不必如此,正如你之前所说,你可以扩展Thread对象并实现public void run方法。如果你想要更有组织和灵活(是的,灵活)的方法,你肯定希望使用Runnable,因为这样做具有明显的代码可重用性。
当我说有组织时,我的意思是很容易维护一个...
Runnable doSomething = new Runnable()
{
    @Override
    public void run()
    {
        goAndDoSomethingReallyHeavyWork();
    }
};

并且可以重复使用相同的runnable对象在另一个线程上或者在之后的同一线程上(是的,你实际上可以重新使用一个Thread),这比将2个或更多的线程扩展为仅使用一次的对象更好。
引用: runnable接口对于使Java线程工作的重要性是什么?
关键是Thread对象“知道”你的Runnable具有一个run方法,并在需要时执行它(并停止、暂停和其他线程操作)。
Java的接口是否继承自某个东西?
这个问题值得我给你点赞。我真的很想知道,但似乎这是语言的一个特性,而不是像每个其他扩展Object超类的对象一样。
希望有所帮助。干杯

感谢您的回复,Bruno Viera。这也非常有帮助。 - stackoverflow

2

就功能而言,实现Runnable接口或扩展Thread类之间没有区别。但是在某些情况下,实现Runnable接口可能更好。想象一下这样的情况:你的类必须继承自其他类,并且还应该显示线程功能。由于你的类无法从多个类继承(Java不支持),因此在这种情况下,你的类可以实现Runnable接口。


1

但是为什么在Java中必须实现一个接口来创建线程?

当你通过继承Thread类创建一个线程时,你将不能再继承其他任何类(多重继承)。 另一方面,如果你使用Runnable接口,则可以获得继承任何类的好处,如果需要的话。

除了上述好处之外,你还可以获得内存和性能方面的优势。


1

ExecutorService.submit​( Runnable task )

您说:

扩展线程

我们不再需要直接使用 Thread 类来并发运行代码。 Java 5 引入了 Executors 框架。请参见 Oracle 的教程

执行者服务管理在一个或多个后台线程上运行您的任务。您可以从几种类型的执行者服务中选择,这些服务是通过 Executors 类实例化的。

对于偶尔的一些短暂任务,请使用由缓存线程池支持的执行者服务。

ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit( yourRunnableObjectGoesHere ) ;
ExecutorService的作用是执行一个名为runcall的方法中的代码。
正如其他正确的答案所解释的那样,Runnable接口的目的是代表一个协议。当你的代码声称实现Runnable接口时,你承诺你的代码有一个名为run的方法。
Java编译器注意到这个承诺并检查是否履行了该协议。如果你传递一个未声明实现Runnable接口且没有一个不带参数且不返回任何值的run方法的对象,则编译器将标记该情况为错误。

因此,执行器服务要求您将任务提交为实现Runnable(或Callable)接口的类的对象,以确保当一个任务到达后台线程上执行时,该任务具有名为run(或Callablecall)的方法。

示例代码

以下是一些示例代码。请注意,执行器服务并不关心您将什么类型的对象传递给其submit方法。您可以传递一个Dog类、SalesReport类或Payroll类的对象——这都无所谓。执行器服务关心的只是传递给submit的对象具有名为run的方法。

package work.basil.example;

import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo
{
    public static void main ( String[] args )
    {
        Demo app = new Demo();
        app.demo();
    }

    private void demo ( )
    {
        Runnable task = new Runnable()
        {
            @Override
            public void run ( )
            {
                System.out.println( "Doing this work on a background thread. " + Instant.now() );
            }
        };

        ExecutorService executorService = null;
        try
        {
            executorService = Executors.newCachedThreadPool();
            executorService.submit( task );
            executorService.submit( task );
            executorService.submit( task );

            // Wait a moment for the background threads to do their work.
            try
            {
                Thread.sleep( Duration.ofSeconds( 2 ).toMillis() );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
        finally
        {
            if ( Objects.nonNull( executorService ) ) { executorService.shutdown(); }
            System.out.println( "Ending the main thread. " + Instant.now() );
        }
    }
}

运行时:

Doing this work on a background thread. 2020-12-20T07:16:26.119414Z
Doing this work on a background thread. 2020-12-20T07:16:26.119176Z
Doing this work on a background thread. 2020-12-20T07:16:26.119255Z
Ending the main thread. 2020-12-20T07:16:28.124384Z

Lambda语法

如果您熟悉现代Java中的Lambda语法,我们可以缩短定义Runnable实现的代码到单行。相同的效果,只是不同的语法。

package work.basil.example;

import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo
{
    public static void main ( String[] args )
    {
        Demo app = new Demo();
        app.demo();
    }

    private void demo ( )
    {
        Runnable task = ( ) -> System.out.println( "Doing this work on a background thread. " + Instant.now() );

        ExecutorService executorService = null;
        try
        {
            executorService = Executors.newCachedThreadPool();
            executorService.submit( task );
            executorService.submit( task );
            executorService.submit( task );

            // Wait a moment for the background threads to do their work.
            try
            {
                Thread.sleep( Duration.ofSeconds( 2 ).toMillis() );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
        finally
        {
            if ( Objects.nonNull( executorService ) ) { executorService.shutdown(); }
            System.out.println( "Ending the main thread. " + Instant.now() );
        }
    }
}

继承

你问道:

Java的接口继承自什么?

Java中所有的类都继承自Object类或继承自Object的其他类。

Java中的接口不继承自任何类。请记住,接口只是一份契约,即某个类可能选择关于具有特定名称、使用特定类型参数并返回特定类型值的方法的承诺。

Java中的接口可以继承一个或多个其他接口。这样做只是为了给声称要实现该接口的类增加更多的方法。请注意,Runnable被两个其他接口RunnableFutureRunnableScheduledFuture所继承。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接