从我在Java中使用线程的时间来看,我发现了两种编写线程的方法:

使用可运行的机具:

public class MyRunnable implements Runnable {
    public void run() {
        //Code
    }
}
//Started with a "new Thread(new MyRunnable()).start()" call

或者,使用扩展线程:

public class MyThread extends Thread {
    public MyThread() {
        super("MyThread");
    }
    public void run() {
        //Code
    }
}
//Started with a "new MyThread().start()" call

这两个代码块有什么显著的区别吗?


当前回答

对于大多数工作线程来说,最好的方法是将线程完全封装在工作线程类中,这样就不会有任何外部干扰,导致不需要的和无效的线程/类状态。

我刚刚发布了一个示例,因此我也将与您分享:

/**
 * This worker can only run once
 * @author JayC667
 */
public class ProperThreading {

    private final Thread        mThread         = new Thread(() -> runWorkingLoop());   // if you want worker to be able to run multiple times, move initialisation into startThread()
    private volatile boolean    mThreadStarted  = false;
    private volatile boolean    mStopRequested  = false;

    private final long          mLoopSleepTime;

    public ProperThreading(final long pLoopSleepTime /* pass more arguments here, store in members */ ) {
        mLoopSleepTime = pLoopSleepTime;
    }

    public synchronized void startThread() {
        if (mThreadStarted) throw new IllegalStateException("Worker Thread may only be started once and is already running!");
        mThreadStarted = true;
        mThread.start();
    }

    private void runWorkingLoop() {
        while (!mStopRequested /* && other checks */ ) {
            try {
                // do the magic work here
                Thread.sleep(mLoopSleepTime);

            } catch (final InterruptedException e) {
                break;
            } catch (final Exception e) {
                // do at least some basic handling here, you should NEVER ignore exception unless you know exactly what you're doing, and then it should be commented!
            }
        }
    }

    public synchronized void stopThread() {
        if (!mThreadStarted) throw new IllegalStateException("Worker Thread is not even running yet!");
        mStopRequested = true;
        mThread.interrupt();
    }

}

其他回答

1.扩展线程接口,就像让类只作为线程一样。您的新类将像一个增强的线程。

jshell> public class Test extends Thread{
   ...> public Test(String name){
   ...> super(name);
   ...> }
   ...> public void run(){
   ...> System.out.println(Thread.currentThread().getName());
   ...> }
   ...> }
|  created class Test

jshell> Test t1=new Test("MyThread");
t1 ==> Thread[MyThread,5,main]

它创建一个线程,而不是Test对象。所以它会像一个线程。不能在线程之间共享Test类的实例。

2.实现可运行接口。

jshell> public class Test1 implements Runnable{
   ...> public void run(){
   ...> System.out.println(Thread.currentThread().getName());
   ...> }
   ...> public String getName(){
   ...> return "testing";}
   ...> }
|  created class Test1

jshell> Test1 t1=new Test1();
t1 ==> Test1@396a51ab  --> this creates Test1 object.

该对象可以通过以下方式跨线程共享,

jshell> Thread t1=new Thread(t1,"Hai");
t ==> Thread[Hai,5,main]

jshell> Thread t=new Thread(t1,"Hai");
t ==> Thread[Hai,5,main]

我认为已经有很多关于这个话题的讨论,认为这可能对基础知识有所帮助。

这就是SOLID的s:单一责任。

线程体现了一段代码的异步执行的运行上下文(如执行上下文:堆栈帧、线程id等)。理想情况下,这段代码应该是相同的实现,无论是同步的还是异步的。

如果将它们捆绑在一个实现中,则会给结果对象两个不相关的更改原因:

应用程序中的线程处理(即查询和修改执行上下文)由代码段(可运行部分)实现的算法

如果您使用的语言支持部分类或多重继承,那么您可以在其自己的超级类中分离每个原因,但归结起来与组成两个对象相同,因为它们的特征集不重叠。这是为了理论。

在实践中,一般来说,一个方案不需要比必要的更复杂。如果您有一个线程在处理一个特定的任务,而不需要更改该任务,那么将任务划分为单独的类可能没有任何意义,并且代码仍然更简单。

在Java环境中,由于该工具已经存在,因此直接从独立的可运行类开始,并将其实例传递给线程(或执行器)实例可能更容易。一旦习惯了这种模式,它就不比简单的可运行线程情况更难使用(甚至读取)。

实现Runnable和扩展Thread之间的一个区别是,通过扩展Thread,每个线程都有一个与其关联的唯一对象,而实现Runnable,许多线程可以共享同一个对象实例。

实现Runnable的类不是线程,只是一个类。对于要由线程执行的Runnable,需要创建一个Thread实例,并将Runnable实例作为目标传入。

在大多数情况下,如果只打算重写run()方法而不打算重写其他Thread方法,则应使用Runnable接口。这一点很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应该被子类化。

当需要扩展超类时,实现Runnable接口比使用Thread类更合适。因为我们可以在实现Runnable接口时扩展另一个类以生成线程。但是如果我们只是扩展Thread类,我们就不能从任何其他类继承。

这里的每个人似乎都认为实现Runnable是一条路,我并不真的不同意他们的观点,但在我看来,扩展Thread也是有道理的,事实上,您已经在代码中演示了这一点。

如果您实现Runnable,那么实现Runnable的类对线程名称没有控制权,调用代码可以设置线程名称,如下所示:

new Thread(myRunnable,"WhateverNameiFeelLike");

但是如果您扩展了Thread,那么您可以在类本身中管理它(就像在您的示例中,您将线程命名为“ThreadB”)。在这种情况下,您:

A) 可能会为调试目的提供一个更有用的名称

B) 强制将该名称用于该类的所有实例(除非您忽略了它是一个线程的事实,并将其作为Runnable来执行上述操作,但我们在这里讨论的是约定,所以可以忽略我认为的可能性)。

例如,您甚至可以对其创建进行堆栈跟踪,并将其用作线程名称。这可能看起来很奇怪,但取决于代码的结构,它对调试非常有用。

这看起来可能是一件小事,但如果你有一个非常复杂的应用程序,其中有很多线程,并且突然“停止”了(可能是因为死锁,也可能是因为网络协议中的一个不太明显的缺陷,或者其他无休止的原因),然后从Java获取堆栈转储,其中所有线程都被称为“线程-1”、“线程-2”、,“Thread-3”并不总是非常有用(这取决于线程的结构,以及您是否可以仅通过它们的堆栈跟踪来有效地区分哪个线程是哪个线程-如果您使用的是多个线程组,它们都运行相同的代码,则不总是可能的)。

尽管如此,您当然也可以通过创建线程类的扩展来以通用的方式执行上述操作,该扩展将其名称设置为其创建调用的堆栈跟踪,然后将其用于Runnable实现而不是标准的java线程类(见下文),但是除了堆栈跟踪之外用于调试的线程名称(对它可能处理的许多队列或套接字之一的引用,例如,在这种情况下,您可能更希望专门针对这种情况扩展thread,以便编译器强制您(或其他使用库的人)传递某些信息(例如,有问题的队列/套接字)以用于名称)。

以下是以调用堆栈跟踪为名称的通用线程的示例:

public class DebuggableThread extends Thread {
    private static String getStackTrace(String name) {
        Throwable t= new Throwable("DebuggableThread-"+name);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(os);
        t.printStackTrace(ps);
        return os.toString();
    }

    public DebuggableThread(String name) {
        super(getStackTrace(name));
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new Thread());
        System.out.println(new DebuggableThread("MainTest"));
    }
}

下面是比较两个名称的输出示例:

Thread[Thread-1,5,main]
Thread[java.lang.Throwable: DebuggableThread-MainTest
    at DebuggableThread.getStackTrace(DebuggableThread.java:6)
    at DebuggableThread.<init>(DebuggableThread.java:14)
    at DebuggableThread.main(DebuggableThread.java:19)
,5,main]

Thread和Runnable的主要区别在于:-线程类似于:Worker(执行Runnable)-可运行的类似于:作业(由线程执行)