Питання "Реалізує Runnable" або "розширює тему"


З того часу, як я провів з потоками в 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

Чи існує суттєва різниця в цих двох блоках коду?


1797
2018-02-12 14:28


походження


Спасибі за це запитання, відповіді дали зрозуміти багато неправильних уявлень. Я подивився правильний спосіб зробити теми Java перед тим, як SO існував, і там було багато дезинформації / застарілої інформації там. - James McMahon
є одна з причин, чому ви можете продовжити Thread (але я не рекомендую це), ви можете попередньо впоратися interrupt(). Знову ж таки, це ідея, вона може бути корисна у правильному випадку, однак я не рекомендую це. - bestsss
Будь ласка, див. Також відповідь, що добре пояснив: stackoverflow.com/q/5562720/285594 - YumYumYum
@ bestsss, я намагаюся сперечатися про те, що ви можете мати на увазі при роботі з перериванням (). Ви намагаєтесь переопределити метод? - Bob Cross
так. За кодом клас Thread A може поширювати будь-який клас, тоді як клас Thread B не може поширювати будь-який інший клас - mani deepak


Відповіді:


Так: реалізує Runnable це найкращий спосіб зробити це, ІМО. Ви насправді не спеціалізуєтеся на поведінці нитки. Ви просто даєте йому щось бігти. Це означає композиція є філософськи "чистий" спосіб піти.

В практичний терміни, це означає, що ви можете реалізувати Runnable і поширюватися також з іншого класу.


1447
2018-02-12 14:32



Точно добре поставить. Яку поведінку ми намагаємося переписати в Thread, продовжуючи її? Я б стверджував, що більшість людей не намагаються переписати будь-яку поведінку, але намагаються використовувати поведінку Thread. - hooknc
У якості стороннього коментаря, якщо ви створюєте тег і не називаєте його методом start () ви створюєте витік пам'яті в Java <5 (це не відбувається з Runnables): stackoverflow.com/questions/107823/... - Nacho Coloma
Однією з незначних переваг Runnable є те, що якщо за певних обставин вам не подобається або не хочеться використовувати threading, і ви просто хочете виконати код, ви маєте можливість просто викликати run (). наприклад, (дуже ручна вага) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run() - user949300
@ user949300 ви також можете це зробити за допомогою extends Thread і якщо ви не хочете, щоб потоки з'являлись, чому б ви навіть реалізували Runnable... - m0skit0
Перефразовуючи Сьєрру і Бейтс, ключовою перевагою реалізації Runnable є те, що ви архітектурно відокремлюєте "роботу" від "бігуна". - 8bitjunkie


tl; dr: implements Runnable - краще. Однак застереження важливо

Загалом я б рекомендував використовувати щось на зразок Runnable а не Thread тому що це дозволяє вам тримати вашу роботу лише вільно разом з вашим вибором паралелізму. Наприклад, якщо ви використовуєте a Runnable і вирішить пізніше, що насправді це не вимагає власного Thread, ви можете просто зателефонувати threadA.run ().

Застереження: Навколо цього я сильно перешкоджаю використанню сировинних ниток. Я набагато віддаю перевагу використанню Календарі і FutureTasks (Від Javadoc: "Анульоване асинхронне обчислення"). Інтеграція тайм-аутів, належного скасування та об'єднання потоків сучасної підтримки паралелізму набагато корисні для мене, ніж купи сировини.

Слідувати: є а FutureTask конструктор що дозволяє використовувати Runnables (якщо це вам найбільше зручно) і як і раніше отримувати вигоду від сучасних інструментів паралелізму. Цитувати явадок:

Якщо вам не потрібен конкретний результат, розгляньте можливість використання конструкцій форми:

Future<?> f = new FutureTask<Object>(runnable, null)

Отже, якщо ми замінимо їх runnable з твоїм threadA, ми отримуємо таке:

new FutureTask<Object>(threadA, null)

Інший варіант, який дозволяє вам залишатися ближче до Runnables, - це ThreadPoolExecutor. Ви можете скористатись виконувати метод передачі в Runnable виконувати "задане завдання колись у майбутньому".

Якщо ви хочете спробувати використовувати пул потоків, фрагмент коду вище стане чимось подібним до наступного (використовуючи Executors.newCachedThreadPool () заводський метод):

ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());

494
2018-02-12 14:37



Це краще, ніж прийнята відповідь IMHO. Одне: фрагмент коду, який ви маєте, не закриває виконавця, і я бачу мільйони питань, де люди це неправильно, створюючи нового Виконавця щоразу, коли вони хочуть створити завдання. es буде краще, як статичне (або введене) поле, так що воно буде створено лише один раз. - artbristol
@artmbrost, спасибі! Я не згоден з новим Виконавцем (ми робимо те, що ви пропонуєте в нашому коді). Написавши оригінальну відповідь, я намагався написати мінімальний код, аналогічний оригінальному фрагменту. Треба сподіватися, що багато читачів цих відповідей використовують їх як стрибки з точок. Я не намагаюся написати заміни для javadoc. Я ефективно пишу тут маркетинговий матеріал: якщо вам сподобався цей метод, ви повинні побачити всі інші чудові речі, які ми можемо запропонувати ...! - Bob Cross
Я знаю, що я трохи пізніше коментував це, але мав справу FutureTask прямо, як правило, не те, що ви хочете зробити. ExecutorServices створить відповідний Future для вас, коли ти submit a Runnable/Callable їм. Аналогічно для ScheduledExecutorServiceS і ScheduledFuture коли ти schedule a Runnable/Callable. - Powerlord
@Powerlord, я мав намір зробити фрагменти коду, які максимально точно відповідали OP. Я згоден з тим, що новий FutureTask не є оптимальним, але це зрозуміло з метою пояснення. - Bob Cross


Мораль історії:

Успадковуйте, лише якщо ви хочете перевизначити деяку поведінку.

А точніше, це слід читати як:

Менш замало, інтерфейс більше.


226
2018-03-11 15:50



Це завжди повинно стати питанням, якщо ви почнете робити одночасно запущений об'єкт! Вам навіть потрібні функції Fun Object? - Liebertee
При успадкуванні від Thread, майже завжди хочеться перевизначити поведінку run() метод - Warren Dew
Ви не можете перевизначити поведінку a java.lang.Thread заважаючи run() метод У цьому випадку вам потрібно перевизначити start() метод я думаю Зазвичай ви просто повторюєте поведінку java.lang.Thread ввівши ваш виконання блоку в run() метод - sura2k


Ну так багато хороших відповідей, я хочу додати більше на це. Це допоможе зрозуміти Extending v/s Implementing Thread.
Розширення зв'язує два файли класу дуже тісно, ​​і це може призвести до деяких проблем із кодом.

Обидва підходи виконують ту саму роботу, але існують деякі відмінності.
Найбільш поширеною є різниця 

  1. Коли ви розширюєте клас Thread, після цього ви не можете розширити будь-який інший клас, який вам потрібно. (Як відомо, Java не дозволяє успадковувати більше одного класу).
  2. Коли ви реалізуєте Runnable, ви можете зберегти пробіл для свого класу для продовження будь-якого іншого класу в майбутньому або зараз.

Однак, один значна різниця між виконанням Runnable і розширення Thread це
  by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instance.

Наступний приклад допомагає зрозуміти це

//Implement Runnable Interface...
 class ImplementsRunnable implements Runnable {

private int counter = 0;

public void run() {
    counter++;
    System.out.println("ImplementsRunnable : Counter : " + counter);
 }
}

//Extend Thread class...
class ExtendsThread extends Thread {

private int counter = 0;

public void run() {
    counter++;
    System.out.println("ExtendsThread : Counter : " + counter);
 }
}

//Use above classes here in main to understand the differences more clearly...
public class ThreadVsRunnable {

public static void main(String args[]) throws Exception {
    // Multiple threads share the same object.
    ImplementsRunnable rc = new ImplementsRunnable();
    Thread t1 = new Thread(rc);
    t1.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    Thread t2 = new Thread(rc);
    t2.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    Thread t3 = new Thread(rc);
    t3.start();

    // Creating new instance for every thread access.
    ExtendsThread tc1 = new ExtendsThread();
    tc1.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    ExtendsThread tc2 = new ExtendsThread();
    tc2.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    ExtendsThread tc3 = new ExtendsThread();
    tc3.start();
 }
}

Вихід з вищевказаної програми.

ImplementsRunnable : Counter : 1
ImplementsRunnable : Counter : 2
ImplementsRunnable : Counter : 3
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1

Підхід під керуванням інтерфейсу Runnable створюється лише один екземпляр класу, який розділяється різними потоками. Таким чином, значення лічильника збільшується для кожного доступу до потоку.

Оскільки, підхід класу Thread, вам доведеться створити окремий екземпляр для кожного доступу до потоку. Отже, для кожних класних екземплярів виділяється інша пам'ять, і кожна з них має окремий лічильник, значення залишається таким же, що означає, що приріст не відбудеться, оскільки жоден з об'єктів не відповідає одному.

Коли використовувати Runnable?
Використовуйте інтерфейс Runnable, якщо ви хочете отримати доступ до одного ресурсу з групи потоків. Уникайте використання класу Thread тут, оскільки створення декількох об'єктів споживає більше пам'яті, і це стає великою продуктивністю накладних витрат.

Клас, який реалізує Runnable, не є потоком і просто класом. Щоб Runnable стати ниткою, вам потрібно створити екземпляр Thread і передати себе як ціль.

У більшості випадків інтерфейс Runnable слід використовувати, якщо ви плануєте лише перевизначити його run() метод і ніяких інших методів потоку. Це важливо, тому що класи не повинні підкласуватися, якщо програміст не має наміру змінювати або посилювати фундаментальне поведінку класу.

Коли існує необхідність розширення суперкласу, реалізація інтерфейсу Runnable є більш доцільним, ніж використання класу Thread. Оскільки ми можемо поширювати ще один клас під час виконання інтерфейсу Runnable, щоб зробити нитку.

Я сподіваюсь, це допоможе!


185
2018-03-05 14:26



Ваш код явно неправильний. Я маю на увазі, що він робить те, що робить, але не те, що мав намір показати. - zEro
Щоб уточнити: для випадкового випадку ви використовували один і той же екземпляр ImplementsRunnable для запуску декількох потоків, тоді як для випадку Thread ви створюєте різні екземпляри ExtendsThread, що явно призводить до поведінки, яку ви показали. Друга половина вашого основного методу повинна бути: ExtendsThread et = new ExtendsThread();  Thread tc1 = new Thread(et);  tc1.start();  Thread.sleep(1000);  Thread tc2 = new Thread(et);  tc2.start();  Thread.sleep(1000);  Thread tc3 = new Thread(et);  tc3.start();  Чи є це яскравішим? - zEro
Я ще не розумію вашого наміру, але я мав на увазі те, що якщо ви створюєте кілька екземплярів ExtendsThread, вони всі повертаються 1 (як показано). Ви можете одержати ті ж результати для Runnable, виконуючи те ж саме, тобто створивши кілька екземплярів ImplementsRunnable. - zEro
@zEro Привіт, я з майбутнього. Враховуючи, що ваша версія коду має Thread збільшується також, це твердження by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instance то неправильно? Якщо ні, то який випадок це показує? - Evil Washing Machine
@ EvilWashingMachine: неактивний протягом довгого часу і просто бачив це. Я додав хеш-код об'єкта до тверджень друку ... + " hashcode: " + this.hashCode() - zEro


Одне, що я здивований, ще не згадувалося, - це реалізація Runnable робить ваш клас більш гнучким.

Якщо ви поширюєте нитку, тоді дія, яку ви виконуєте, завжди буде в потоці. Однак, якщо ви реалізуєте Runnable це не повинно бути. Ви можете запустити його в потоці або передати його до певної служби виконувача або просто передавати його як завдання в межах одного поточного додатку (можливо, буде запущено пізніше, але в межах одного потоку). Варіанти набагато більш відкриті, якщо ви просто використовуєте Runnable ніж якщо ти зв'яжешся з собою Thread.


73
2018-02-12 14:51



Ну, ви дійсно можете зробити те ж саме з a Thread об'єкт теж тому Thread implements Runnable... ;-) Але це "відчуває себе краще" робити ці речі з Runnable ніж робити їх з a Thread! - siegi
Правда, але Thread додає багато додаткових речей, які вам не потрібні, і в багатьох випадках не хочуть. Вам завжди краще реалізувати інтерфейс, який відповідає тому, що ви насправді робите. - Herms


Якщо ви хочете реалізувати або розширити будь-який інший клас, то Runnable Інтерфейс є найбільш оптимальним, якщо ви не хочете, щоб будь-який інший клас продовжував чи впроваджував тоді Thread клас переважно

Найбільш поширеною є різниця

enter image description here

Коли ти extends Thread клас, після чого ви не можете продовжити будь-який інший клас, який вам потрібен. (Як відомо, Java не дозволяє успадковувати більше одного класу).

Коли ти implements Runnable, ви можете зберегти пробіл для свого класу, щоб продовжити будь-який інший клас у майбутньому чи зараз.

  • Java не підтримує декілька спадщин, а це означає, що ви можете розширити лише один клас в Java, тому як тільки ви розширили клас Thread, ви втратили шанс і не зможете розширити або наслідувати інший клас у Java.

  • У об'єктно-орієнтованому програмуванні розширення класу зазвичай означає додавання нової функціональності, модифікації або вдосконалення поведінки. Якщо ми не робимо ніяких змін у Thread, замість цього використовуйте інтерфейс Runnable.

  • Runnable інтерфейс являє собою завдання, яке може виконувати будь-який простий Thread або Виконавці або будь-яким іншим способом. тому логічне розділення завдання як Runnable, ніж Thread є гарним дизайнерським рішенням.

  • Розділення завдання як Runnable означає, що ми можемо знову використати завдання, а також маємо свободу виконувати його за допомогою різних засобів. оскільки ви не можете перезапустити тему після її завершення. знову Runnable vs Thread для завдання, Runnable є переможцем.

  • Дизайнер Java визнає це, і тому Виконавці приймають Runnable як завдання, і у них є робочий потік, який виконує ці завдання.

  • Успадкування всіх методів Thread є додатковими накладними просто для представлення завдання, яке можна легко зробити з Runnable.

Courtesy from javarevisited.blogspot.com

Це були деякі помітні відмінності між Thread and Runnable в Java, якщо ви знаєте інші відмінності в Thread vs Runnable, ніж поділіться ним через коментарі. Я особисто використовую Runnable over Thread для цього сценарію та рекомендую використовувати Runnable або Callable інтерфейс на основі вашої вимоги.

Однак істотна різниця полягає в тому,

Коли ти extends Thread клас, кожен ваша гілка створює унікальний об'єкт і пов'язаний з ним. Коли ти implements Runnable, він поділяє той самий об'єкт на кілька потоків.


65
2018-05-11 08:59





Насправді, не варто порівнювати Runnable і Thread один з одним.

Ці два мають залежність і взаємозв'язок у багатопоточці так само, як Wheel and Engine відносини автомобільного транспорту.

Я б сказав, що існує лише один спосіб для багатопотокової обробки з двома кроками. Дозвольте мені висловити свою думку.

Runnable:
При реалізації interface Runnable це означає, що ви створюєте щось, що є run able в іншому потоці. Тепер, створюючи те, що може запускатися всередині потоку (пробій у межах потоку), не означає створення нитки.
Так клас MyRunnable це не що інше, як звичайний клас з a void run метод І це об'єкти будуть деякими звичайними об'єктами лише за допомогою методу run який зазвичай виконуватиметься при виклику. (якщо ми не передаємо об'єкт у потоці).

Тема:
class Thread, Я б сказав: дуже спеціальний клас з можливістю запуску нової нитки, яка фактично дозволяє багатопотокові через його start() метод

Чому не мудрі порівняти?
Тому що нам потрібні обидва з них для багатопотокових.

Для Multi-threading ми потребуємо двох речей:

  • Щось, що може запускатися всередині потоку (Runnable).
  • Щось, що може почати нову нитку (нитку).

Технічно і теоретично, обидва з них необхідні для початку потоку, одна буде біжи і хочеш запустити його (Люблю Wheel and Engine автомобіля).

Ось чому ви не можете запустити гілку з MyRunnable ви повинні передати його до екземпляра Thread.

Але можна створити та запустити потік лише за допомогою class Thread тому що Клас Thread реалізує Runnable тому всі ми знаємо Thread також є a Runnable всередині

Нарешті Thread і Runnable доповнюють один одного для багатопотокового режиму, а не конкурента або заміни.


61
2018-05-12 13:50



Точно! Це має бути прийнята відповідь. До речі я думаю, що питання було відредаговано і ThreadA вже не має сенсу - idelvall
прийнята відповідь набагато більше делегованих спасибі за вашу відповідь @idelvall - Saif


Ви повинні реалізувати Runnable, але якщо ви працюєте на Java 5 або вище, не слід починати з нього new Thread але використовуйте Експерт-сервіс замість цього. Докладніше див .: Як реалізувати просте вторнювання в Java.


41
2018-02-12 14:41



Я б не думав, що ExecutorService буде корисним, якщо ви просто хочете запустити єдиний потік. - Powerlord
З того, що я навчився, більше не слід починати самостійно в цілому, тому що залишення цього для служби-виконавця робить все набагато більш керованим (наприклад, очікуючи призупинення потоку). Крім того, я не бачу нічого в цьому питанні, що передбачає, що мова йде про одну нитку. - Fabian Steeg
Яка точка використання будь-якої багатопотокової версії, якщо ми знаємо, апріор, що це буде єдиний потік. Отже, давайте припустимо, що у нас є кілька потоків, і ця відповідь є цінною. - zEro
@zEro Я впевнений, що є причина, що існує лише одна тема відправлення події. Я сумніваюся, що це єдиний випадок, коли краще мати окрему гілку, але, можливо, не найкраще мати кілька. - porcoesphino