"Реалізує Runnable" проти "розширює нитку" в Java


2118

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

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


57
Дякуючи за це запитання, відповіді усунули багато помилок, які у мене були. Я розглядав правильний спосіб створення потоків Java до існування SO, і там було багато дезінформації / застарілої інформації.
Джеймс Макмахон

5
є одна причина, з якої ви можете продовжити тему ( але я не рекомендую її ), ви можете попередньо впоратися interrupt(). Знову ж таки, це ідея, вона може бути корисною в правильному випадку, проте я не рекомендую її.
bestsss

Будь ласка , дивіться також відповідь, добре пояснив: stackoverflow.com/q/5562720/285594

@bestsss, я намагаюся розгадати, що ти можеш мати на увазі щодо обробки переривання (). Ви намагаєтесь замінити метод?
Боб Крос

8
так. Відповідно до коду, нитка класу A може розширити будь-який клас, тоді як клас B нитка B не може поширити будь-який інший клас
mani deepak

Відповіді:


1672

Так: втілення Runnable- це кращий спосіб зробити це, IMO. Ти не дуже спеціалізується на поведінці нитки. Ви просто даєте йому щось запустити. Це означає, що композиція - філософськи "чистіший" шлях.

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


151
Точно, добре кажучи. Яку поведінку ми намагаємося перезаписати в Thread, розширивши її? Я зауважу, що більшість людей не намагаються перезаписати будь-яку поведінку, а намагаються використовувати поведінку теми.
підключення

87
В якості побічного коментаря, якщо ви створили тему і не викликаєте її метод start (), ви створюєте витік пам'яті в Java <5 (цього не відбувається з Runnables): stackoverflow.com/questions/107823/…
Nacho Coloma

25
Одна незначна перевага Runnable полягає в тому, що якщо за певних обставин ви не переймаєтесь або не хочете використовувати потоки, а ви просто хочете виконати код, у вас є можливість просто зателефонувати run (). наприклад (дуже handwavy) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
користувач949300

10
@ user949300 Ви також можете це зробити, extends Threadі якщо ви не хочете Runnable
вводити нитки,

30
Якщо перефразовувати Сьєрру та Бейтса, ключовою перевагою впровадження Runnable є те, що ви архітектурно відокремлюєте «роботу» від «бігуна».
8bitjunkie

569

tl; dr: реалізує Runnable краще. Однак застереження важливе

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

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

Наступні дії: існує FutureTaskконструктор, який дозволяє використовувати Runnables (якщо це вам найбільше комфортно) і все-таки отримувати переваги від сучасних інструментів одночасності. Щоб цитувати javadoc :

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

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

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

new FutureTask<Object>(threadA, null)

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

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

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

40
Це краще, ніж прийнята відповідь ІМХО. Одне: фрагмент коду, який ви маєте, не закриває виконавця, і я бачу мільйони запитань, де люди помиляються, створюючи нового Виконавця кожного разу, коли вони хочуть породити завдання. esбуло б краще як статичне (або введене) поле, тому воно створюється лише один раз.
artbristol

7
@artbristol, дякую! Я не погоджуюся з новим Виконавцем (ми робимо те, що ви пропонуєте в нашому коді). Пишучи оригінальну відповідь, я намагався написати мінімальний код, аналогічний оригінальному фрагменту. Треба сподіватися, що багато читачів цих відповідей використовують їх як стрибки балів. Я не намагаюся писати заміну на javadoc. Я ефективно пишу маркетингові матеріали для цього: якщо вам сподобається цей метод, ви повинні побачити всі інші чудові речі, які ми можемо запропонувати ...!
Боб Крос

3
Я знаю, що я трохи пізно коментую це, але спілкуватися FutureTaskбезпосередньо - це не те, що ти хочеш робити. ExecutorServices створить відповідну Futureдля вас , коли ви / ім. Так само і для s, і коли ви a / . submitRunnableCallableScheduledExecutorServiceScheduledFuturescheduleRunnableCallable
Powerlord

3
@Powerlord, мій намір полягав у тому, щоб зробити фрагменти коду, які максимально відповідали ОП. Я погоджуюся, що новий FutureTask не є оптимальним, але це зрозуміло для цілей пояснення.
Боб Крос

257

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

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

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

Наслідуйте менше, інтерфейс більше.


1
Це завжди має бути питанням, якщо ви починаєте створювати одночасно запущений Об'єкт! Вам навіть потрібні функції об’єкта теми?
Лібертей

4
При успадкуванні від теми, майже завжди хочеться змінити поведінку run()методу.
Warren Dew

1
Ви не можете змінити поведінку java.lang.Threadатрибута, замінивши run()метод. У такому випадку вам потрібно скасувати start()метод, який я думаю. Зазвичай ви просто повторно використовуєте поведінку java.lang.Thread, вводячи у свій run()метод виконання блок .
sura2k

Спадщина не тільки для того, щоб перекрити якусь поведінку, це також використовувати загальну поведінку. І це навпаки, чим більше відмінок, тим гірша ієрархія.
peja

232

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

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

  1. Розширюючи клас теми, після цього ви не можете розширити жоден інший клас, який вам потрібен. (Як відомо, 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 the 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 для створення потоку.

Сподіваюся, це допоможе!


40
Ваш код явно невірний. Я маю на увазі, це робить те, що робить, але не те, що ви мали намір показати.
zEro

38
Для уточнення: для запущеного випадку ви використовували той самий екземпляр 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

19
Я ще не розумію вашого наміру, але я висловив те, що якщо ви створите кілька екземплярів ExtendsThread - всі вони повернуть 1 (як ви показали). Ви можете отримати однакові результати для Runnable, зробивши там одне і те ж, тобто створивши кілька екземплярів ImplementsRunnable.
zEro

3
@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неправильне? Якщо ні, то який випадок демонструє це?
Зла пральна машина

4
Розміщений тут код вводить в оману і може призвести до головного болю. Дякуємо @zEro, що трохи очистив його.
Нікос

78

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

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


6
Ну, ви також можете зробити те ж саме з Threadоб'єктом, тому що Thread implements Runnable... ;-) Але це "почуває себе краще" робити ці речі з a, Runnableніж робити їх з a Thread!
siegi

7
Щоправда, але Threadдодає багато зайвих речей, які вам не потрібні, і в багатьох випадках не хочуть. Вам завжди краще реалізувати інтерфейс, який відповідає тому, що ви насправді робите.
Гермс

74

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

Найпоширеніша різниця

введіть тут опис зображення

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

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

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

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

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

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

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

  • Успадкування всіх методів теми - це додаткові накладні витрати лише для представлення завдання, яке легко виконати за допомогою Runnable.

Люб’язно з javarevisited.blogspot.com

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

Однак істотна різниця є.

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


73

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

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

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

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

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

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

Для мультиварки нам потрібно дві речі:

  • Щось, що може працювати всередині теми (Виконується).
  • Щось, що може почати нову тему (Thread).

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

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

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

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


3
Саме так! Це має бути прийнятою відповіддю. До речі, я думаю, що це питання було відредаговано і ThreadAбільше не має сенсу
idelvall

прийнята відповідь набагато більше делегат дякує за вашу відповідь @idelvall
Сайф

Найкраща відповідь! Дякую!
MichaelYe

44

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


5
Я не думаю, що ExecutorService буде такою корисною, якщо ви просто хочете запустити один потік.
Powerlord

1
З того, що я дізнався, більше не слід запускати потік самостійно, оскільки передача цього в службу виконавця робить все набагато більш контрольованим (наприклад, очікування призупинення потоку). Крім того, у питанні я нічого не бачу, що б це стосувалося однієї теми.
Fabian Steeg

4
У чому сенс використання будь-якої багатопотокової нитки, якщо ми знаємо, що це буде одна нитка. Тож припустимо, що у нас є кілька ниток, і ця відповідь цінна.
zEro

1
@zEro Я впевнений, що є причина, що є лише одна нитка розсилки подій. Я сумніваюся, що це єдиний випадок, коли найкраще мати окрему нитку, але, можливо, не найкраще мати декілька.
porcoesphino

33

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

Редагувати. Первісно сказано: "Для реалізації інтерфейсу потрібно менше ресурсів". також, але вам потрібно створити новий екземпляр Thread в будь-якому випадку, тому це було неправильно.


Ми не можемо здійснювати дзвінки в мережі, чи не так? Як у мене є android.os.NetworkOnMainThreadException. Але за допомогою потоку я можу здійснювати мережеві дзвінки. Будь ласка, виправте мене, якщо я помиляюся.
Набіль Тобані

@NabeelThobani Звичайна Java не хвилює, але це звучить так, як робить Android. Однак я недостатньо знайомий з Android.
Пауерлорд

1
@NabeelThobani Звичайно, можна. Можливо, ви не створюєте нитку за допомогою програми Runnable.
m0skit0

20

Я б сказав, що існує третій спосіб:

public class Something {

    public void justAnotherMethod() { ... }

}

new Thread(new Runnable() {
   public void run() {
    instanceOfSomething.justAnotherMethod();
   }
}).start();

Можливо, на це трохи впливає моє недавнє велике використання Javascript та Actionscript 3, але таким чином вашому класу не потрібно реалізовувати досить розпливчастий інтерфейс, як Runnable.


38
Це насправді не третій спосіб. Ви все ще реалізуєте Runnable, просто робите це анонімно.
Дон Робі

2
@Don Roby: Що відрізняється. Це часто зручно, і ви можете використовувати поля та остаточні локальні змінні із вмісту класу / методу.
Барт ван Хекелом

6
@BartvanHeukelom Це зручно, але не відрізняється. Це можна зробити за допомогою будь-якого типу вкладеного класу, тобто внутрішніх класів, локальних класів та лямбда-виразів.
xehpuk

18

З випуском Java 8 з'явився третій варіант.

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

Ваш приклад можна замінити на:

new Thread(() -> { /* Code here */ }).start()

або якщо ви хочете використовувати ExecutorServiceпосилання на метод і:

executor.execute(runner::run)

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


Ця відповідь потребує пояснення. Після деякого спантеличення я роблю висновок, що () -> {}повинно представляти власну логіку, яка комусь потрібна? Так що краще сказати як () -> { /* Code here */ }?
ToolmakerSteve

17

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


12

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

Якщо ви реалізуєте Runnable, то клас, який реалізує Runnable, не контролює ім'я потоку, саме ім'я потоку може встановити ім'я потоку, наприклад:

new Thread(myRunnable,"WhateverNameiFeelLike");

але якщо ви розширите Thread, ви отримаєте можливість керувати цим у самому класі (так само, як у вашому прикладі ви називаєте нитку "ThreadB"). У цьому випадку ви:

A) може дати йому більш корисну назву для цілей налагодження

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

Можна навіть, наприклад, взяти слід стека його створення і використовувати це як ім’я потоку. Це може здатися дивним, але залежно від структури вашого коду він може бути дуже корисним для налагодження.

Це може здатися дрібницею, але там, де у вас дуже складний додаток з великою кількістю потоків, і раптом все "зупинилося" (або з причини тупика, або, можливо, через недолік мережевого протоколу, який був би меншим Очевидно - або з інших нескінченних причин), то отримання дампів стека з Java, де всі потоки називаються "Thread-1", "Thread-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]

cHao, що ти думаєш? Ви не могли використовувати свій код вище під час виконання потоку, щоб отримати стек-трій створення потоків (замість цього ви отримаєте просте ім'я або в кращому випадку стек-ланцюг запуску потоків), але, використовуючи потік підкласифікації, ви можете зробити саме це і примусити це навіть вимагає подальшої конкретної контекстної інформації, тим самим даючи вам більш конкретне розуміння того, яка саме тема може виникнути.
AntonyM

8
Моя думка полягає в тому, що "Якщо ви реалізуєте Runnable, то клас, який реалізує Runnable, не має контролю над назвою потоку ..." явно помилковий. Реалізація класу Runnableдійсно може керувати іменем потоку, оскільки потік, що виконує код, за визначенням є поточним потоком (і будь-який код, який передає перевірки безпеки, має контроль над іменами потоків). Зважаючи на те, що ви присвячуєте половину своєї публікації "omg, а як з іменами ниток!", Це здається якоюсь великою справою.
cHao

Назва нитки? Ніщо не перешкоджає і поширенню класу потоків.
RichieHH

11

Виконується через:

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

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


11

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

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

  2. У тому ж світлі, якщо вам не потрібно , щоб успадковують методи потоків, можна обійтися і без цього над головою , використовуючи Runnable.

  3. Однократне успадкування : Якщо ви розширюєте Thread, ви не можете поширитись з будь-якого іншого класу, тому, якщо це те, що вам потрібно зробити, ви повинні використовувати Runnable.

  4. Хороша конструкція відокремлювати логіку домену від технічних засобів, і в цьому сенсі краще виконати завдання Runnable, що ізолює ваше завдання від вашого бігуна .

  5. Ви можете виконати один і той же об’єкт Runnable декілька разів , проте об'єкт Thread можна запустити лише один раз. (Можливо, причина, чому Виконавці приймають Runnables, але не Нитки.)

  6. Якщо ви розробляєте своє завдання як Runnable, ви маєте всю гнучкість, як ним користуватися зараз і в майбутньому . Ви можете запускати його одночасно через Виконавців, але також через Тему. І ви все одно можете також використовувати / називати його одночасно в одному потоці, як і будь-який інший звичайний тип / об'єкт.

  7. Це також спрощує розділення аспектів логіки завдання та одночасності у ваших тестових одиницях .

  8. Якщо вас зацікавило це запитання, можливо, вас також зацікавить різниця між дзвінками та програмою .


@Pino Так, нитка сама по собі також є Runnable. Однак якщо розширити його, щоб просто використовувати його як Runnable, який сенс? Чому б просто не використовувати звичайний Runnable без усього багажу. Отже, я заперечую, що якщо ви продовжите Thread, ви також виконали б її за допомогою методу запуску, який можна використовувати лише один раз. Саме в цьому відповідь хотів зробити Нідхіш-Крішнан. Зауважте, що моя - це лише компіляція або короткий підсумок інших відповідей тут.
Йорг


8

Про це йдеться у Підручнику з визначення та започаткування теми Oracle :

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

Іншими словами, реалізація Runnableбуде працювати в сценаріях, коли ваш клас поширює клас, відмінний від Thread. Java не підтримує багаторазове успадкування. Крім того, розширення Threadбуде неможливим при використанні деяких API API управління потоком високого рівня. Єдиний сценарій, коли розширення Threadє кращим - це невелика програма, яка не підлягає оновленню в майбутньому. Це майже завжди краще реалізувати, Runnableоскільки він гнучкіший у міру зростання вашого проекту. Зміна дизайну не матиме великого впливу, оскільки ви можете реалізувати багато Java-інтерфейсів, але розширити лише один клас.


8

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

Наприклад, припустимо, що є два потоки, thread1 ставить ціле число в масив, а thread2 приймає цілі числа з масиву, коли масив заповнюється. Зауважте, що для того, щоб потік2 працював, він повинен знати стан масиву, заповнював його потік чи ні.

Реалізація Runnableдозволяє вам мати цю гнучкість для спільного використання об'єкта, тоді як extends Threadзмушує створювати нові об'єкти для кожної теми, тому будь-яке оновлення, яке робиться за допомогою теми thread1, втрачається на thread2.


7

Якщо я не помиляюся, це більш-менш схоже на

Чим відрізняється інтерфейс від абстрактного класу?

extends встановлює співвідношення " Є A ", а інтерфейс забезпечує можливість " Має ".

Переважно реалізує Runnable :

  1. Якщо вам не потрібно розширювати клас Thread і змінювати реалізацію за замовчуванням API Thread
  2. Якщо ви виконуєте команду пожежі та забудьте
  3. Якщо ви вже продовжуєте інший клас

Краще " розширює нитку ":

  1. Якщо вам доведеться перекрити будь-який з цих методів теми, як зазначено на сторінці документації oracle

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

З іншого боку, використання розширеного ExecutorServiceабо ThreadPoolExecutorServiceAPI забезпечує більшу гнучкість та контроль.

Подивіться на це питання SE:

ExecutorService vs Casual Thread Spawner


5

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


5

Це S з SOLID : Single відповідальність.

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

Якщо з'єднати їх разом в одну реалізацію, ви отримаєте отриманий об'єкт дві непов'язані причини зміни:

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

Якщо мова, якою ви користуєтеся, підтримує часткові класи або багатократне успадкування, то ви можете відокремити кожну причину у своєму власному суперкласі, але вона зводиться до того ж, що складається з двох об'єктів, оскільки їх набори функцій не збігаються. Це для теорії.

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

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


5

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

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


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

5

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


5

Чи можемо ми знову відвідати основну причину того, як хотіли, щоб наш клас поводився як такий Thread? Причин взагалі немає, ми просто хотіли виконати завдання, швидше за все, в асинхронному режимі, а саме це означає, що виконання завдання має відгалужуватися від нашої головної нитки та головної нитки, якщо закінчується рано, може бути, а може і не чекати для розгалуженого шляху (завдання).

Якщо в цьому вся мета, то де я бачу потребу в спеціалізованій нитці. Це можна досягти, взявши нитку RAW з пулу ниток системи та призначивши її нашою задачею (може бути екземпляром нашого класу), і це все.

Тож дотримуємось концепції OOPs і запишемо клас потрібного нам типу. Існує багато способів зробити речі, якщо це зробити правильно.

Нам потрібно завдання, тому напишіть визначення завдання, яке можна виконати на Нитці. Тому використовуйте Runnable.

Завжди пам’ятайте implements, що спеціально використовується для надання поведінки таextends використовується для передачі функції / властивості.

Ми не хочемо властивості потоку, натомість хочемо, щоб наш клас поводився як завдання, яке можна запустити.


4

Так, якщо ви викликаєте виклик ThreadA, тоді не потрібно викликати метод запуску, а метод запуску - це виклик лише після виклику класу ThreadA. Але якщо використовувати виклик ThreadB, тоді необхідно запустити початковий потік для методу запуску виклику. Якщо у вас є ще допомога, відповідайте мені.


4

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


4

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

Наприклад: Якщо ви створюєте аплет, він повинен розширювати клас Applet, тому єдиний спосіб створити потік - це реалізувати інтерфейс Runnable


4

Runnableє інтерфейсом, в той час Threadяк клас, який реалізує цей інтерфейс. З точки зору проектування, має бути чіткий поділ між тим, як визначено завдання, і між тим, як воно виконується. Перша - це відповідальність Runnalbeвпровадження, а остання - робота Threadкласу. У більшості випадків реалізація Runnable- це правильний шлях.


4

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

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


4

Додаю сюди два мої центи - завжди, коли можливо implements Runnable. Нижче наведено два застереження щодо того, чому не слід використовувати extends Threads

  1. В ідеалі вам ніколи не слід розширювати клас теми; Threadклас повинен бути зроблений final. Принаймні, його методи, як thread.getId(). Дивіться цю дискусію щодо помилки, пов’язаної із розширенням Threads.

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

Перегляньте http://pastebin.com/BjKNNs2G .

public class WaitPuzzle {

    public static void main(String[] args) throws InterruptedException {
        DoNothing doNothing = new DoNothing();
        new WaitForever(doNothing).start();
        new WaitForever(doNothing).start();
        new WaitForever(doNothing).start();
        Thread.sleep(100);
        doNothing.start();
        while(true) {
            Thread.sleep(10);
        }
    }


    static class WaitForever extends  Thread {

        private DoNothing doNothing;

        public WaitForever(DoNothing doNothing) {
            this.doNothing =  doNothing;
        }

        @Override
        public void run() {
            synchronized (doNothing) {
                try {
                    doNothing.wait(); // will wait forever here as nobody notifies here
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Unreachable Code");
            }
        }
    }

    static class DoNothing extends Thread {

        @Override
        public void run() {
            System.out.println("Do Nothing ");
        }
    } 
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.