Câu hỏi "Triển khai Runnable" và "mở rộng Thread"


Từ thời gian tôi đã dành cho các chủ đề trong Java, tôi đã tìm thấy hai cách để viết chủ đề:

Với implements Runnable:

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

Hoặc với extends Thread:

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

Có bất kỳ sự khác biệt đáng kể nào trong hai khối mã này không?


1797
2018-02-12 14:28


gốc


Cảm ơn câu hỏi này, các câu trả lời đã giải thích rất nhiều quan niệm sai lầm mà tôi có. Tôi đã xem xét một cách chính xác để tạo các luồng Java trước khi SO tồn tại và có rất nhiều thông tin sai lệch / lỗi thời trên mạng. - James McMahon
có một lý do bạn có thể muốn mở rộng Thread (nhưng tôi không khuyên bạn nên), bạn có thể xử lý trước interrupt(). Một lần nữa, đó là một ý tưởng, nó có thể hữu ích trong trường hợp đúng, tuy nhiên tôi không khuyên bạn nên nó. - bestsss
Xin vui lòng xem thêm câu trả lời, giải thích độc đáo: stackoverflow.com/q/5562720/285594 - YumYumYum
@bestsss, tôi đang cố gắng để tìm ra những gì bạn có thể có nghĩa là về xử lý ngắt (). Bạn đang cố gắng ghi đè phương pháp? - Bob Cross
yes.As cho mỗi mã, lớp Thread A có thể mở rộng bất kỳ lớp nào trong khi lớp Thread B không thể mở rộng bất kỳ lớp nào khác - mani deepak


Các câu trả lời:


Có: thực hiện Runnable là cách ưa thích để làm điều đó, IMO. Bạn không thực sự chuyên về hành vi của luồng. Bạn chỉ cần cho nó một cái gì đó để chạy. Điêu đo co nghia la thành phần là thuộc về triết học "purer" con đường để đi.

Trong thực tế các điều khoản, điều đó có nghĩa là bạn có thể triển khai Runnable và mở rộng từ một lớp khác.


1447
2018-02-12 14:32



Chính xác, đặt tốt. Chúng ta đang cố gắng ghi đè hành vi nào trong Chủ đề bằng cách mở rộng nó? Tôi cho rằng hầu hết mọi người không cố gắng ghi đè lên bất kỳ hành vi nào, nhưng cố gắng sử dụng hành vi của Chủ đề. - hooknc
Là một bình luận phụ, nếu bạn khởi tạo một Thread và không gọi phương thức start () của nó, bạn đang tạo ra một rò rỉ bộ nhớ trong Java <5 (điều này không xảy ra với Runnables): stackoverflow.com/questions/107823/… - Nacho Coloma
Một lợi thế nhỏ của Runnable là, nếu trong một số trường hợp bạn không quan tâm, hoặc không muốn sử dụng luồng, và bạn chỉ muốn thực thi mã, bạn có tùy chọn chỉ cần gọi run (). ví dụ. (rất nhạy cảm) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run() - user949300
@ user949300 bạn cũng có thể làm điều đó với extends Thread và nếu bạn không muốn luồng tại sao bạn thậm chí sẽ thực hiện Runnable... - m0skit0
Để diễn giải Sierra và Bates, một lợi ích quan trọng của việc thực hiện Runnable là bạn đang phân chia kiến ​​trúc về "job" từ "runner". - 8bitjunkie


tl; dr: thực hiện Runnable là tốt hơn. Tuy nhiên, báo trước là quan trọng

Nói chung, tôi khuyên bạn nên sử dụng một cái gì đó như Runnable thay vì Thread bởi vì nó cho phép bạn duy trì công việc của mình một cách lỏng lẻo cùng với lựa chọn đồng thời của bạn. Ví dụ: nếu bạn sử dụng Runnable và sau đó quyết định rằng điều này không thực sự yêu cầu nó Thread, bạn chỉ có thể gọi threadA.run ().

Caveat: Xung quanh đây, tôi mạnh mẽ không khuyến khích việc sử dụng các chủ đề thô. Tôi thích sử dụng Các cuộc gọi và FutureTasks (Từ javadoc: "Tính toán không đồng bộ có thể hủy"). Sự tích hợp của timeout, hủy bỏ thích hợp và các thread threading của sự hỗ trợ đồng thời hiện đại là tất cả hữu ích hơn cho tôi hơn đống nguyên liệu.

Theo sát: đây là một FutureTask constructor cho phép bạn sử dụng Runnables (nếu đó là những gì bạn cảm thấy thoải mái nhất) và vẫn nhận được lợi ích của các công cụ đồng thời hiện đại. Để báo giá javadoc:

Nếu bạn không cần một kết quả cụ thể, hãy xem xét sử dụng các công trình xây dựng của biểu mẫu:

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

Vì vậy, nếu chúng ta thay thế runnable với bạn threadA, chúng tôi nhận được những điều sau đây:

new FutureTask<Object>(threadA, null)

Một tùy chọn khác cho phép bạn ở gần hơn với Runnables là một ThreadPoolExecutor. Bạn có thể dùng thi hành phương pháp để vượt qua trong Runnable để thực hiện "các nhiệm vụ nhất định đôi khi trong tương lai."

Nếu bạn muốn thử sử dụng một hồ bơi thread, đoạn mã trên sẽ trở thành một cái gì đó như sau (sử dụng Executors.newCachedThreadPool () phương pháp nhà máy):

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

494
2018-02-12 14:37



Điều này là tốt hơn so với câu trả lời được chấp nhận IMHO. Một điều: đoạn mã bạn đã không đóng cửa các giám đốc điều hành và tôi thấy hàng triệu câu hỏi mà mọi người nhận được điều này sai, tạo ra một Executor mới mỗi khi họ muốn sinh ra một nhiệm vụ. es sẽ tốt hơn như một trường tĩnh (hoặc được tiêm) để nó chỉ được tạo một lần. - artbristol
@artbristol, cảm ơn! Tôi không đồng ý với Người thực thi mới (chúng tôi làm những gì bạn đề xuất trong mã của chúng tôi). Trong văn bản câu trả lời ban đầu, tôi đã cố gắng để viết tối thiểu mã analagous đến đoạn ban đầu. Chúng ta phải hy vọng rằng nhiều độc giả của những câu trả lời này sử dụng chúng như là nhảy ra khỏi các điểm. Tôi không cố gắng viết thay thế cho javadoc. Tôi đang viết tài liệu tiếp thị hiệu quả cho nó: nếu bạn thích phương pháp này, bạn sẽ thấy tất cả những điều tuyệt vời khác mà chúng tôi phải cung cấp ...! - Bob Cross
Tôi biết tôi hơi muộn nhận xét về điều này, nhưng đối phó với FutureTask trực tiếp nói chung không phải là những gì bạn muốn làm. ExecutorServices sẽ tạo ra thích hợp Future cho bạn khi bạn submit một Runnable/Callable đối với họ. Tương tự như vậy cho ScheduledExecutorServicecát ScheduledFuture khi bạn schedule một Runnable/Callable. - Powerlord
@Powerlord, ý định của tôi là tạo các đoạn mã phù hợp với OP càng gần càng tốt. Tôi đồng ý rằng FutureTask mới không phải là tối ưu nhưng rõ ràng cho các mục đích giải thích. - Bob Cross


Đạo đức của câu chuyện:

Kế thừa chỉ khi bạn muốn ghi đè lên một số hành vi.

Hay đúng hơn nó nên được đọc là:

Thừa hưởng ít hơn, giao diện nhiều hơn.


226
2018-03-11 15:50



Điều này luôn luôn là câu hỏi nếu bạn bắt đầu thực hiện một đối tượng đang chạy đồng thời! Bạn thậm chí cần các chức năng của đối tượng Thread? - Liebertee
Khi kế thừa từ Chủ đề, hầu như luôn muốn ghi đè hành vi của run() phương pháp. - Warren Dew
Bạn không thể ghi đè hành vi của một java.lang.Thread bằng cách ghi đè run() phương pháp. Trong trường hợp đó, bạn cần ghi đè lên start() phương pháp tôi đoán. Thông thường bạn chỉ cần sử dụng lại hành vi của java.lang.Thread bằng cách tiêm khối thực thi của bạn vào run() phương pháp. - sura2k


Vâng rất nhiều câu trả lời tốt, tôi muốn thêm nhiều hơn về điều này. Điều này sẽ giúp hiểu Extending v/s Implementing Thread.
Mở rộng liên kết hai tệp lớp rất chặt chẽ và có thể gây ra một số khó khăn để xử lý mã.

Cả hai cách tiếp cận đều làm cùng một công việc nhưng đã có một số khác biệt.
Sự khác biệt phổ biến nhất là 

  1. Khi bạn mở rộng lớp Thread, sau đó bạn không thể mở rộng bất kỳ lớp nào khác mà bạn yêu cầu. (Như bạn đã biết, Java không cho phép kế thừa nhiều hơn một lớp).
  2. Khi bạn thực hiện Runnable, bạn có thể tiết kiệm một không gian cho lớp của bạn để mở rộng bất kỳ lớp nào khác trong tương lai hoặc bây giờ.

Tuy nhiên, một sự khác biệt đáng kể giữa việc triển khai Runnable và mở rộng Thread là
  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.

Ví dụ sau giúp bạn hiểu rõ hơn

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

Đầu ra của chương trình trên.

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

Trong cách tiếp cận giao diện Runnable, chỉ có một thể hiện của một lớp đang được tạo ra và nó đã được chia sẻ bởi các luồng khác nhau. Vì vậy, giá trị của bộ đếm được tăng lên cho mỗi và mọi truy cập luồng.

Trong khi đó, cách tiếp cận lớp Thread, bạn phải tạo cá thể riêng biệt cho mỗi truy cập luồng. Do đó bộ nhớ khác nhau được phân bổ cho mỗi trường hợp lớp và mỗi bộ đếm có bộ đếm riêng, giá trị vẫn giữ nguyên, có nghĩa là không có sự gia tăng nào xảy ra vì không có tham chiếu đối tượng nào giống nhau.

Khi nào cần sử dụng Runnable?
Sử dụng giao diện Runnable khi bạn muốn truy cập cùng một tài nguyên từ nhóm chủ đề. Tránh sử dụng lớp Thread ở đây, vì việc tạo nhiều đối tượng tiêu thụ nhiều bộ nhớ hơn và nó trở thành một chi phí hiệu năng lớn.

Một lớp thực hiện Runnable không phải là một luồng và chỉ là một lớp. Đối với Runnable để trở thành một Thread, bạn cần phải tạo một thể hiện của Thread và chuyển chính nó vào như là đích.

Trong hầu hết các trường hợp, giao diện Runnable nên được sử dụng nếu bạn chỉ có kế hoạch ghi đè lên run() và không có phương thức Thread nào khác. Điều này quan trọng vì các lớp không nên được phân lớp trừ khi lập trình viên dự định sửa đổi hoặc tăng cường hành vi cơ bản của lớp.

Khi có nhu cầu mở rộng một siêu lớp, việc triển khai giao diện Runnable thích hợp hơn là sử dụng lớp Thread. Bởi vì chúng ta có thể mở rộng một lớp khác trong khi thực hiện giao diện Runnable để tạo một luồng.

Hy vọng điều này có thể giúp cho bạn!


185
2018-03-05 14:26



Mã của bạn là sai. Ý tôi là, nó làm những gì nó làm, nhưng không phải những gì bạn dự định thể hiện. - zEro
Để làm rõ: đối với trường hợp runnable bạn đã sử dụng cùng một cá thể ImplementsRunnable để bắt đầu nhiều luồng, trong khi đối với trường hợp Chủ đề bạn đang tạo các cá thể ExtendsThread khác nhau rõ ràng dẫn đến hành vi mà bạn đã hiển thị. Phần thứ hai của phương pháp chính của bạn nên là: 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();  Nó có rõ ràng hơn không? - zEro
Tôi chưa hiểu ý định của bạn, nhưng quan điểm của tôi là nếu bạn tạo nhiều phiên bản của ExtendsThread - tất cả chúng sẽ trả về 1 (như bạn đã chỉ ra). Bạn có thể nhận được kết quả tương tự cho Runnable bằng cách làm điều tương tự ở đó, tức là tạo nhiều phiên bản của ImplementsRunnable. - zEro
@zEro Xin chào, tôi đến từ tương lai. Do phiên bản mã của bạn có Thread tăng lên, là tuyên bố 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 sau đó sai? Nếu không, thì trường hợp nào thể hiện điều này? - Evil Washing Machine
@EvilWashingMachine: Không hoạt động trong thời gian dài .. và chỉ thấy điều này. Tôi đã thêm mã băm của đối tượng vào các câu lệnh in ... + " hashcode: " + this.hashCode() - zEro


Một điều mà tôi ngạc nhiên chưa được đề cập đến là triển khai Runnable làm cho lớp của bạn trở nên linh hoạt hơn.

Nếu bạn mở rộng chuỗi thì hành động bạn đang thực hiện sẽ luôn nằm trong chuỗi. Tuy nhiên, nếu bạn triển khai Runnable nó không phải là. Bạn có thể chạy nó trong một luồng, hoặc chuyển nó tới một loại dịch vụ thực thi nào đó, hoặc chỉ chuyển nó thành một nhiệm vụ trong một ứng dụng luồng đơn (có thể chạy sau này, nhưng trong cùng một luồng). Các tùy chọn sẽ mở ra nhiều hơn nếu bạn chỉ sử dụng Runnable hơn là bạn tự ràng buộc mình Thread.


73
2018-02-12 14:51



Vâng, bạn thực sự có thể làm điều tương tự với Thread cũng vì Thread implements Runnable… ;-) Nhưng nó "cảm thấy tốt hơn" làm điều này với một Runnable hơn là làm chúng với một Thread! - siegi
Đúng, nhưng Thread bổ sung thêm nhiều thứ mà bạn không cần, và trong nhiều trường hợp không muốn. Bạn luôn tốt hơn khi triển khai giao diện phù hợp với những gì bạn đang thực sự làm. - Herms


Nếu bạn muốn thực hiện hoặc mở rộng bất kỳ lớp nào khác thì Runnable giao diện là thích hợp nhất khác khôn ngoan nếu bạn không muốn bất kỳ lớp khác để mở rộng hoặc thực hiện sau đó Thread lớp học là thích hợp hơn

Sự khác biệt phổ biến nhất là

enter image description here

Khi bạn extends Thread lớp học, sau đó bạn không thể mở rộng bất kỳ lớp nào khác mà bạn yêu cầu. (Như bạn đã biết, Java không cho phép kế thừa nhiều hơn một lớp).

Khi bạn implements Runnable, bạn có thể tiết kiệm một không gian cho lớp của mình để mở rộng bất kỳ lớp nào khác trong tương lai hoặc ngay bây giờ.

  • Java không hỗ trợ đa kế thừa, có nghĩa là bạn chỉ có thể mở rộng một lớp trong Java vì vậy khi bạn mở rộng lớp Thread bạn đã mất cơ hội và không thể mở rộng hoặc kế thừa một lớp khác trong Java.

  • Trong lập trình hướng đối tượng mở rộng một lớp học thường có nghĩa là thêm chức năng mới, sửa đổi hoặc cải thiện hành vi. Nếu chúng ta không thực hiện bất kỳ sửa đổi nào trên Thread thì hãy sử dụng giao diện Runnable thay thế.

  • Giao diện Runnable đại diện cho một nhiệm vụ có thể được thực hiện bởi một trong hai đồng bằng Thread hoặc Executors hoặc bất kỳ phương tiện nào khác. cách phân tách logic của Task như Runnable hơn Thread là quyết định thiết kế tốt.

  • Tách nhiệm vụ là Runnable có nghĩa là chúng ta có thể tái sử dụng nhiệm vụ và cũng có quyền tự do thực hiện nó từ các phương tiện khác nhau. vì bạn không thể khởi động lại một Thread khi nó hoàn thành. một lần nữa Runnable vs Thread cho nhiệm vụ, Runnable là người chiến thắng.

  • Nhà thiết kế Java nhận ra điều này và đó là lý do tại sao các Executors chấp nhận Runnable as Task và họ có chuỗi công nhân thực hiện các nhiệm vụ đó.

  • Kế thừa tất cả các phương thức Thread là phí bổ sung chỉ để đại diện cho một Task có thể được thực hiện dễ dàng với Runnable.

Được phép từ javarevisited.blogspot.com

Đây là một số sự khác biệt đáng chú ý giữa Thread và Runnable trong Java, nếu bạn biết bất kỳ sự khác biệt nào khác về Thread vs Runnable hơn là hãy chia sẻ nó thông qua các bình luận. Cá nhân tôi sử dụng Runnable over Thread cho kịch bản này và khuyến cáo sử dụng giao diện Runnable hoặc Callable dựa trên yêu cầu của bạn.

Tuy nhiên, sự khác biệt đáng kể là.

Khi bạn extends Thread lớp, mỗi chủ đề của bạn tạo đối tượng duy nhất và liên kết với nó. Khi bạn implements Runnable, nó chia sẻ cùng một đối tượng với nhiều luồng.


65
2018-05-11 08:59





Trên thực tế, nó không phải là khôn ngoan để so sánh Runnable và Thread với nhau.

Điều này có một sự phụ thuộc và mối quan hệ trong đa luồng giống như Wheel and Engine mối quan hệ của xe cơ giới.

Tôi sẽ nói, chỉ có một cách để đa luồng với hai bước. Hãy để tôi làm cho quan điểm của tôi.

Runnable:
Khi triển khai interface Runnable nó có nghĩa là bạn đang tạo ra cái gì đó run able trong một chủ đề khác. Bây giờ tạo ra một cái gì đó mà có thể chạy bên trong một thread (runnable bên trong một thread), không có nghĩa là tạo một Thread.
Vậy lớp học MyRunnable chẳng là gì ngoài một lớp bình thường với void run phương pháp. Và đối tượng của nó sẽ là một số đối tượng thông thường chỉ với một phương pháp run sẽ thực thi bình thường khi được gọi. (trừ khi chúng ta vượt qua đối tượng trong một chủ đề).

Chủ đề:
class Thread, Tôi sẽ nói một lớp rất đặc biệt với khả năng bắt đầu một chủ đề mới mà thực sự cho phép đa luồng thông qua start() phương pháp.

Tại sao không khôn ngoan để so sánh?
Bởi vì chúng ta cần cả hai cho đa luồng.

Đối với Đa luồng, chúng ta cần hai thứ:

  • Một cái gì đó có thể chạy bên trong một Thread (Runnable).
  • Cái gì đó có thể bắt đầu một chủ đề mới (Thread).

Về mặt kỹ thuật và lý thuyết, cả hai đều cần thiết để bắt đầu một chủ đề, chạy và một sẽ làm cho nó chạy (Như Wheel and Engine của xe có động cơ).

Đó là lý do tại sao bạn không thể bắt đầu một chuỗi với MyRunnable bạn cần truyền nó cho một thể hiện Thread.

Nhưng có thể tạo và chạy một luồng chỉ bằng cách sử dụng class Thread vì lớp Thread dụng cụ Runnable vì vậy chúng ta đều biết Thread cũng là một Runnable phía trong.

Cuối cùng Thread và Runnable được bổ sung cho nhau để đa luồng không phải là đối thủ cạnh tranh hoặc thay thế.


61
2018-05-12 13:50



Chính xác! Đây sẽ là câu trả lời được chấp nhận. BTW Tôi nghĩ câu hỏi đã được chỉnh sửa và ThreadA không còn ý nghĩa - idelvall
câu trả lời được chấp nhận là nhiều hơn đại biểu cảm ơn cho bạn phản ứng @idelvall - Saif


Bạn nên thực hiện Runnable, nhưng nếu bạn đang chạy trên Java 5 hoặc cao hơn, bạn không nên bắt đầu nó với new Thread nhưng sử dụng ExecutorService thay thế. Để biết chi tiết, hãy xem: Cách triển khai luồng đơn giản trong Java.


41
2018-02-12 14:41



Tôi sẽ không nghĩ rằng ExecutorService sẽ hữu ích nếu bạn chỉ muốn khởi chạy một chuỗi đơn lẻ. - Powerlord
Từ những gì tôi đã học được một nên không còn bắt đầu một thread của riêng bạn nói chung, bởi vì để lại cho các dịch vụ thực hiện làm cho tất cả các điều khiển nhiều hơn nữa (như, chờ đợi cho các chủ đề để đình chỉ). Ngoài ra, tôi không thấy bất cứ điều gì trong câu hỏi ngụ ý nó về một sợi đơn. - Fabian Steeg
Điều gì là điểm của việc sử dụng bất kỳ đa luồng nếu chúng ta biết aprior rằng nó sẽ là một chủ đề duy nhất. Vì vậy, chúng ta hãy giả sử chúng ta có nhiều chủ đề và câu trả lời này là có giá trị. - zEro
@zEro Tôi chắc chắn có một lý do chỉ có một Chủ đề công văn sự kiện. Tôi nghi ngờ đó là trường hợp duy nhất là tốt nhất để có một sợi riêng biệt nhưng có thể không tốt nhất để có nhiều. - porcoesphino


Tôi không phải là một chuyên gia, nhưng tôi có thể nghĩ ra một lý do để thực hiện Runnable thay vì mở rộng Thread: Java chỉ hỗ trợ duy nhất kế thừa, vì vậy bạn chỉ có thể mở rộng một lớp.

Chỉnh sửa: Điều này ban đầu cho biết "Triển khai giao diện yêu cầu ít tài nguyên hơn". là tốt, nhưng bạn cần phải tạo một thể hiện Thread mới, do đó, điều này là sai.


31
2018-02-12 14:32



Trong runnable chúng ta không thể thực hiện cuộc gọi mạng, phải không? Vì tôi đang có android.os.NetworkOnMainThreadException. Nhưng bằng cách sử dụng thread tôi có thể thực hiện cuộc gọi mạng. Hãy sửa tôi nếu tôi sai. - Nabeel Thobani
@NabeelThobani Java bình thường không quan tâm, nhưng nó giống như Android. Mặc dù vậy, tôi chưa đủ quen thuộc với Android. - Powerlord
@NabeelThobani Tất nhiên bạn có thể. Có lẽ bạn không tạo ra một Thread với Runnable của bạn. - m0skit0