"реализует Runnable" против "расширяет поток" в Java - PullRequest
1941 голосов
/ 12 февраля 2009

Сколько времени я потратил на потоки в Java, я нашел два способа написания потоков:

С implements Runnable:

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

Или с extends Thread:

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

Есть ли существенная разница в этих двух блоках кода?

Ответы [ 42 ]

4 голосов
/ 24 апреля 2016

Лучший способ для большинства рабочих потоков состоит в том, чтобы иметь полную инкапсуляцию потока в рабочем классе, чтобы ничто не могло вмешиваться извне и вызывать нежелательные и недопустимые состояния потока / класса.

Я только что опубликовал пример, поэтому я поделюсь этим с вами:

/**
 * 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();
    }

}
3 голосов
/ 18 августа 2012

Runnable - это интерфейс, а Thread - это класс, который реализует этот интерфейс. С точки зрения дизайна, должно быть четкое разделение между тем, как задача определена, и тем, как она выполняется. Первая является ответственностью Runnalbe реализации, а вторая - работой класса Thread. В большинстве случаев реализация Runnable - верный способ следовать.

3 голосов
/ 26 июля 2015

Поток содержит поведение, которое не предназначено для доступа;

  • это синхронизированная блокировка используется для объединения и т.д.
  • есть методы, к которым вы можете получить доступ случайно.

однако, если вы относитесь к подклассу Thread, нужно учесть, что реализован дополнительный поток.

public class ThreadMain {
    public int getId() {
        return 12345678;
    }

    public String getName() {
        return "Hello World";
    }

    public String getState() {
        return "testing";
    }

    public void example() {
        new Thread() {
            @Override
            public void run() {
                System.out.println("id: "+getId()+", name: "+getName()+", state: "+getState());
            }
        }.start();
    }

    public static void main(String[] args) {
        new ThreadMain().example();
    }
}

Если вы запустите это, вы можете ожидать

id: 12345678, name: Hello World, state: testing

однако, вы не вызываете методы, которые вы думаете, потому что вы используете метод в Thread, а не ThreadMain, и вместо этого вы видите что-то вроде

id: 11, name: Thread-0, state: RUNNABLE
2 голосов
/ 23 апреля 2017

Расширяя класс потока, производный класс не может расширять любой другой базовый класс, потому что java допускает только одиночное наследование. напротив, реализуя работающий интерфейс, класс по-прежнему расширяет другой базовый класс.

Наиболее существенная разница между реализацией Runnable и расширением Thread приведена ниже:

Расширяя Thread, производный класс сам является объектом потока, тогда как реализующий интерфейс Runnable разделяет один и тот же объект для нескольких потоков.

2 голосов
/ 10 ноября 2016

В редком случае, если вы запускаете его только один раз, вам следует расширить поток из-за СУХОГО. Если вы вызываете его несколько раз, вы должны реализовать Runnable, потому что тот же поток не должен быть перезапущен.

2 голосов
/ 06 июля 2012

Возможно, это не ответ, но в любом случае; есть еще один способ создания тем:

Thread t = new Thread() {
    public void run() {
        // Code here
    }
}
1 голос
/ 22 октября 2017

Класс потока определяет несколько методов, которые могут быть overriden расширяющим классом. Но чтобы создать поток, мы должны переопределить метод run(). То же относится и к Runnable.

Однако Runnable является предпочтительным методом создания потока. Основные причины:

  1. Поскольку Runnable - это интерфейс, вы можете расширять другие классы. Но если вы расширяете поток, то эта опция исчезнет.

  2. Если вы не модифицируете или не расширяете множество функций Thread и расширение класса Thread не является предпочтительным способом.

1 голос
/ 03 мая 2019

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]

Я думаю, что уже много обсуждалось по этой теме. хотя это может быть полезно в основах.

1 голос
/ 18 июня 2014

Простой способ сказать это: Если вы реализуете интерфейс, это означает, что вы реализуете все его методы, и если вы расширяете класс, вы наследуете метод по вашему выбору ... В этом случае существует только один метод с именем Run () , поэтому лучше для реализации интерфейса Runnable.

0 голосов
/ 02 сентября 2018

Я бы сказал, что текущая задача отделена от потока. Мы можем передать задачу в Thread, среду Executor и т. Д. В случае Runnable, в то время как с расширением Thread задача связана с самим объектом потока. Изоляция задачи не может быть выполнена в случае расширения Thread. Это похоже на то, что мы записываем задачу в объект Thread, просто что-то вроде микросхемы (и, более конкретно, не получим никакой ручки для задачи).

...