Phần mềm thực hiện Runnable


2119

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

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ó sự khác biệt đáng kể nào trong hai khối mã này không?


57
Cảm ơn câu hỏi này, các câu trả lời đã làm sáng tỏ rất nhiều quan niệm sai lầm mà tôi có. Tôi đã xem xét cách chính xác để thực hiện 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 / thông tin lỗi thời ngoài kia.
James McMahon

5
có một lý do bạn có thể muốn mở rộng Chủ đề ( nhưng tôi không khuyến nghị điều đó ), 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 phù hợp, tuy nhiên tôi không khuyên bạn nên dùng nó.
bestsss

Vui lòng xem thêm câu trả lời, giải thích độc đáo: stackoverflow.com/q/5562720/285594

@bestsss, tôi đang cố gắng tìm hiểu ý nghĩa của bạn về việc xử lý ngắt (). Bạn đang cố gắng ghi đè phương thức?
Bob Cross

8
đúng. Theo 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âu trả lời:


1673

Có: thực hiện Runnablelà 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 chủ đề. Bạn chỉ cần cho nó một cái gì đó để chạy. Điều đó có nghĩa là sáng tác là con đường "tinh khiết" hơn về mặt triết học .

Trong điều kiện thực tế , nó có nghĩa là bạn có thể thực hiện Runnablevà mở rộng từ một lớp khác.


151
Chính xác, cũng đặt. Hành vi nào chúng ta đang cố gắng ghi đè trong Thread 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 đè bất kỳ hành vi nào, nhưng cố gắng sử dụng hành vi của Thread.
hooknc

87
Là một nhận xét phụ, nếu bạn khởi tạo một Chủ đề và không gọi phương thức start () của nó, bạn đang tạo rò rỉ bộ nhớ trong Java <5 (điều này không xảy ra với Runnables): stackoverflow.com/questions/107823/ Kẻ
Nacho Coloma

25
Một lợi thế nhỏ của Runnable là, 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 thuận tay) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
user949300

10
@ user949300 bạn cũng có thể làm điều đó với extends Threadvà nếu bạn không muốn xâu chuỗi tại sao bạn thậm chí sẽ triển khai Runnable...
m0skit0

30
Để diễn giải Sierra và Bates, một lợi ích chính của việc triển khai Runnable là bạn đang phân tách "công việc" về mặt kiến ​​trúc từ "người chạy".
8bitjunkie

569

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

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

Hãy cẩn thận: Xung quanh đây, tôi không khuyến khích việc sử dụng Chủ đề thô. Tôi rất thích sử dụng CallablesFutureT Nhiệm vụ (Từ javadoc: "Một tính toán không đồng bộ có thể hủy được"). Việc tích hợp thời gian chờ, hủy hợp lý và tổng hợp luồng của hỗ trợ đồng thời hiện đại đều hữu ích hơn nhiều đối với tôi so với hàng đống Chủ đề thô.

Theo dõi: có một nhà FutureTaskxây dựng cho phép bạn sử dụng Runnables (nếu đó là điều 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ụ tương tranh hiện đại. Để trích dẫn 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ấu trúc của biểu mẫu:

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

Vì vậy, nếu chúng tôi thay thế chúng runnablebằng của bạn threadA, chúng tôi nhận được như sau:

new FutureTask<Object>(threadA, null)

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

Nếu bạn muốn thử sử dụng nhóm luồng, đoạn mã ở trên sẽ trở thành một thứ giống như sau (sử dụng phương thức xuất xưởng của Executors.newCachedThreadPool () ):

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

40
Điều này tốt hơn câu trả lời được chấp nhận IMHO. Một điều: đoạn mã bạn không đóng trình thực thi và tôi thấy hàng triệu câu hỏi mà mọi người hiểu sai, tạo ra một Executor mới mỗi khi họ muốn sinh ra một nhiệm vụ. essẽ tốt hơn khi là trường tĩnh (hoặc được chèn) để nó chỉ được tạo một lần.
artbristol

7
@artbristol, cảm ơn! Tôi không đồng ý với Executor mới (chúng tôi làm những gì bạn đề xuất trong mã của chúng tôi). Khi viết câu trả lời ban đầu, tôi đã cố gắng viết mã tối thiểu cho đoạn gốc. Chúng tôi 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 làm điểm nhảy ra. Tôi không cố viết một thay thế cho javadoc. Tôi đang viết tài liệu tiếp thị cho nó một cách hiệu quả: 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

3
Tôi biết tôi hơi chậm nhận xét về vấn đề này, nhưng FutureTasknói chung trực tiếp không phải là điều bạn muốn làm. ExecutorServices sẽ tạo ra sự phù hợp Futurecho bạn khi bạn submita Runnable/ Callablevới họ. Tương tự như vậy cho ScheduledExecutorServices và ScheduledFuturekhi bạn schedulea Runnable/ Callable.
Powerlord

3
@Powerlord, ý định của tôi là tạo ra các đoạn mã khớp với OP càng sát càng tốt. Tôi đồng ý rằng FutureTask mới không tối ưu nhưng rõ ràng cho mục đích giải thích.
Bob Cross

257

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

Chỉ kế thừa nếu bạn muốn ghi đè một số hành vi.

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

Kế thừa ít hơn, giao diện nhiều hơn.


1
Đây luôn là câu hỏi nếu bạn bắt đầu tạo một Object chạy đồng thời! Bạn thậm chí có cần các chức năng Thread Object không?
Liebertee

4
Khi kế thừa từ Thread, người ta gần như luôn muốn ghi đè hành vi của run()phương thức.
Warren Dew

1
Bạn không thể ghi đè hành vi của a java.lang.Threadbằng cách ghi đè run()phương thức. Trong trường hợp đó, bạn cần ghi đè start()phương thức 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.Threadbằng cách đưa khối thực thi của bạn vào run()phương thức.
sura2k

Sự kế thừa không chỉ để ghi đè một số hành vi, mà còn là sử dụng các hành vi phổ biến. Và nó ngược lại, càng ghi đè, hệ thống phân cấp càng tệ hơn.
peja

232

Vâng rất nhiều câu trả lời hay, 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ó 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 triển khai Runnable, bạn có thể tiết kiệm 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ờ.

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

Đầ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 phiên bản của một lớp được tạo 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 một thể hiện riêng cho mỗi lần truy cập luồng. Do đó bộ nhớ khác nhau được phân bổ cho mỗi phiên bản lớp và mỗi bộ đếm có bộ đếm riêng, giá trị vẫn giữ nguyên, điều đó 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 nê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 các luồng. Tránh sử dụng lớp Thread ở đây, vì việc tạo nhiều đối tượng tiêu tốn 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 chủ đề và chỉ là một lớp. Để Runnable trở thành một Thread, Bạn cần tạo một thể hiện của Thread và tự chuyển nó thành mục tiêu.

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ỉ dự định ghi đè run()phương thức và không có phương thức Thread nào khác. Điều này rất 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 có ý đị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 siêu lớp, việc thực hiện giao diện Runnable sẽ phù hợp hơn so với 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!


40
Mã của bạn là sai một cách rõ ràng. Ý tôi là, nó làm những gì nó làm, nhưng không phải những gì bạn định thể hiện.
zEro

38
Để làm rõ: đối với trường hợp có thể chạy được, bạn đã sử dụng cùng một đối tượng ImplementsRunnable để bắt đầu nhiều luồng, trong khi đó đối với trường hợp Thread bạn đang tạo các trường hợp ExtendsThread khác nhau dẫn đến hành vi bạn đã thể hiện. Nửa sau của phương thức chính của bạn sẽ 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

19
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 đã hiển thị). Bạn có thể nhận được kết quả tương tự cho Runnable bằng cách thực hiện điều tương tự ở đó, tức là tạo nhiều phiên bản ImplementsRunnable.
zEro

3
@zEro Xin chào, tôi đến từ tương lai. Cho rằng phiên bản mã của bạn cũng Threadtăng lên, câu lệnh có by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instancesai không? Nếu không, thì một trường hợp chứng minh điều này là gì?
Máy giặt Evil

4
Mã được đăng ở đây là sai lệch và có thể dẫn đến đau đầu. Cảm ơn @zEro vì đã xóa nó đi một chút.
Nikos

78

Một điều mà tôi ngạc nhiên chưa được đề cập đến là việc triển khai Runnablelàm cho lớp của bạ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 ở trong một luồng. Tuy nhiên, nếu bạn thực hiện Runnablethì không cần phải như vậy. Bạn có thể chạy nó trong một luồng, hoặc chuyển nó đến một loại dịch vụ thực thi nào đó, hoặc chỉ chuyển nó xung quanh như một tác vụ trong một ứng dụng luồng đơn (có thể được chạy sau đó, nhưng trong cùng một luồng). Các tùy chọn sẽ mở hơn rất nhiều nếu bạn chỉ sử dụng Runnablehơn là nếu bạn tự ràng buộc mình Thread.


6
Chà, bạn thực sự cũng có thể làm điều tương tự với một Threadđối tượng bởi vì Thread implements Runnable;;) Nhưng nó "cảm thấy tốt hơn" khi làm những việc này với a Runnablehơn là làm chúng với a Thread!
siegi

7
Đúng, nhưng Threadthêm rất nhiều thứ bạn không cần, và trong nhiều trường hợp không muốn. Bạn luôn luôn tốt hơn khi thực hiện giao diện phù hợp với những gì bạn đang làm.
Herms

74

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ì Runnablegiao diện là tốt nhất, nếu không, nếu bạn không muốn bất kỳ lớp nào khác mở rộng hoặc thực hiện thì Threadlớp đó là thích hợp hơn.

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

nhập mô tả hình ảnh ở đây

Khi bạn extends Thread 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 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 bây giờ.

  • Java không hỗ trợ nhiều kế thừa, điều đó có nghĩa là bạn chỉ có thể mở rộng một lớp trong Java nên một khi bạn mở rộng lớp Thread, bạn sẽ 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, việc mở rộng một lớp thường có nghĩa là, thêm chức năng mới và sửa đổi hoặc cải thiện các hành vi. Nếu chúng tôi 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 thi bởi Thread hoặc Executors đơn giản hoặc bất kỳ phương tiện nào khác. Vì vậy, phân tách hợp lý của Nhiệm vụ như Runnable hơn Thread là một quyết định thiết kế tốt.

  • Tách nhiệm vụ thành Runnable có nghĩa là chúng ta có thể sử dụng lại nhiệm vụ và cũng có quyền tự do thực thi nó từ các phương tiện khác nhau. vì bạn không thể khởi động lại một Chủ đề 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 Executor chấp nhận Runnable là Nhiệm vụ và họ có luồng công nhân thực thi các nhiệm vụ đó.

  • Kế thừa tất cả các phương thức Thread là chi phí bổ sung chỉ để thể hiện một Nhiệm vụ 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ố 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 trên Thread vs Runnable hơn xin vui lòng chia sẻ nó 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 bạn nên 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 Threadlớp, mỗi luồng của bạn tạo ra một đố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 cho nhiều chủ đề.


73

Thật ra, không khôn ngoan khi so sánh RunnableThreadvới nhau.

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

Tôi muốn nói, chỉ có một cách để đa luồng với hai bước. Hãy để tôi đưa ra quan điểm của mình.

Runnable:
Khi thực hiện interface Runnablenó có nghĩa là bạn đang tạo một cái gì đó run abletrong một luồng khác. Bây giờ tạo một cái gì đó có thể chạy bên trong một luồng (có thể chạy bên trong một luồng), không có nghĩa là tạo ra một Chủ đề.
Vì vậy, lớp MyRunnablekhông có gì ngoài một lớp bình thường với một void runphương thức. Và các đối tượng của nó sẽ là một số đối tượng bình thường chỉ có một phương thức runsẽ thực thi bình thường khi được gọi. (trừ khi chúng ta truyền đối tượng trong một luồng).

Thread :
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 Thread mới thực sự cho phép đa luồng thông qua start()phương thức của nó .

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 điều:

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

Vì vậy, về mặt kỹ thuật và lý thuyết, cả hai đều cần thiết để bắt đầu một chuỗi, một sẽ chạy và một sẽ làm cho nó chạy (Giống như Wheel and Enginexe cơ giới).

Đó là lý do tại sao bạn không thể bắt đầu một chủ đề với MyRunnablebạn cần chuyển nó đến một thể hiện của Thread.

Nhưng nó có thể tạo và chạy một sợi chỉ sử dụng class Threadvì lớp Threadcụ Runnablevì vậy chúng tôi đều biết Threadcũng là một Runnablebên trong.

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


3
Chính xác! Đây phải là câu trả lời được chấp nhận. BTW Tôi nghĩ rằng câu hỏi đã được chỉnh sửa và ThreadAkhông còn ý nghĩa nữa
idelvall 22/03/2016

câu trả lời được chấp nhận là đại biểu nhiều hơn cảm ơn bạn đã trả lời @idelvall
Saif

Câu trả lời tốt nhất! Cảm ơn!
MichaelYe

44

Bạn nên triển khai Runnable, nhưng nếu bạn đang chạy trên Java 5 trở lên, bạn không nên khởi động nó new Threadmà hãy sử dụng ExecutorService thay thế. Để biết chi tiết, xem: Cách triển khai luồng đơn giản trong Java .


5
Tôi không nghĩ ExecutorService sẽ hữu ích nếu bạn chỉ muốn khởi chạy một chuỗi.
Powerlord

1
Từ những gì tôi đã học được, người ta không nên tự mình bắt đầu một chủ đề, bởi vì việc để điều đó cho dịch vụ thực thi sẽ khiến tất cả dễ kiểm soát hơn (như, chờ đợi chủ đề tạm dừng). Ngoài ra, tôi không thấy bất cứ điều gì trong câu hỏi ngụ ý về một chủ đề duy nhất.
Fabian Steeg

4
Điểm quan trọng của việc sử dụng bất kỳ đa luồng nào là gì nếu chúng ta biết người chiến binh rằng đó sẽ là một luồng đơn. Vì vậy, giả sử chúng ta có nhiều chủ đề và câu trả lời này là có giá trị.
zEro

1
@zEro Tôi khá chắc chắn rằng 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 chủ đề riêng biệt nhưng có thể không tốt nhất để có nhiều chủ đề.
porcoesphino

33

Tôi không phải là chuyên gia, nhưng tôi có thể nghĩ ra một lý do để triển khai Runnable thay vì mở rộng Thread: Java chỉ hỗ trợ kế thừa duy nhất, 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 "Việc thực hiện một giao diện đòi hỏi ít tài nguyên hơn." cũng vậy, nhưng bạn cần phải tạo một thể hiện Thread mới, vì vậy điều này là sai.


Trong runnable chúng ta không thể thực hiện cuộc gọi mạng, phải không? Như tôi đang có android.os.NetworkOnMainThreadException. Nhưng bằng cách sử dụng chủ đề, tôi có thể thực hiện cuộc gọi mạng. Xin 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 có vẻ như Android thì có. Mặc dù vậy, tôi không đủ quen thuộc với Android.
Powerlord

1
@NabeelThobani Tất nhiên là có thể. Có thể bạn không tạo Chủ đề bằng Runnable của mình.
m0skit0

20

Tôi muốn nói có một cách thứ ba:

public class Something {

    public void justAnotherMethod() { ... }

}

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

Có thể điều này bị ảnh hưởng một chút bởi việc sử dụng Javascript và Actioncript 3 gần đây của tôi, nhưng theo cách này, lớp của bạn không cần phải thực hiện một giao diện khá mơ hồ như thế nào Runnable.


38
Đây không thực sự là một cách thứ ba. Bạn vẫn đang triển khai Runnable, chỉ cần thực hiện ẩn danh.
Don Roby

2
@Don Roby: Cái nào cũng khác. Nó thường thuận tiện và bạn có thể sử dụng các trường và các biến cục bộ cuối cùng từ lớp / phương thức chứa.
Bart van Heukelom

6
@BartvanHeukelom Thật tiện lợi, nhưng không khác biệt. Bạn có thể làm điều này với bất kỳ loại lớp lồng nhau nào, tức là các lớp bên trong, các lớp cục bộ và các biểu thức lambda.
xehpuk

18

Với việc phát hành Java 8, giờ đây đã có tùy chọn thứ ba.

Runnablelà một giao diện chức năng , có nghĩa là các thể hiện của nó có thể được tạo bằng các biểu thức lambda hoặc tham chiếu phương thức.

Ví dụ của bạn có thể được thay thế bằng:

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

hoặc nếu bạn muốn sử dụng một ExecutorServicetham chiếu phương thức:

executor.execute(runner::run)

Đây không chỉ là ngắn hơn nhiều so với ví dụ của bạn, nhưng cũng đi kèm với nhiều ưu điểm được nêu trong câu trả lời khác của việc sử dụng Runnablehơn Thread, chẳng hạn như trách nhiệm duy nhất và sử dụng phần vì bạn không chuyên hành vi của chủ đề. Cách này cũng tránh tạo thêm một lớp nếu tất cả những gì bạn cần là Runnablenhư bạn làm trong các ví dụ của mình.


Câu trả lời này cần giải thích. Sau một số khó hiểu, tôi kết luận rằng () -> {}được cho là đại diện cho logic tùy chỉnh mà ai đó cần? Vì vậy, nó sẽ được nói tốt hơn như () -> { /* Code here */ }?
ToolmakerSteve

17

Khởi tạo một giao diện giúp phân tách rõ ràng hơn giữa mã của bạn và việc triển khai các luồng, vì vậy tôi muốn triển khai Runnable trong trường hợp này.


12

Mọi người ở đây dường như nghĩ rằng việc triển khai Runnable là cách tốt nhất và tôi không thực sự không đồng ý với họ nhưng cũng có trường hợp mở rộng Thread theo quan điểm của tôi, thực tế là bạn đã chứng minh điều đó trong mã của mình.

Nếu bạn triển khai Runnable thì lớp thực hiện Runnable không có quyền kiểm soát tên luồng, đó là mã gọi có thể đặt tên luồng, như vậy:

new Thread(myRunnable,"WhateverNameiFeelLike");

nhưng nếu bạn mở rộng Thread thì bạn có thể quản lý điều này trong chính lớp đó (giống như trong ví dụ của bạn, bạn đặt tên cho chủ đề là 'ThreadB'). Trong trường hợp này bạn:

A) có thể đặt cho nó một tên hữu ích hơn cho mục đích gỡ lỗi

B) đang buộc tên đó được sử dụng cho tất cả các phiên bản của lớp đó (trừ khi bạn bỏ qua thực tế rằng đó là một luồng và thực hiện các thao tác trên với nó như thể nó là Runnable nhưng chúng ta đang nói về quy ước ở đây trong mọi trường hợp có thể bỏ qua khả năng đó tôi cảm thấy).

Ví dụ, bạn thậm chí có thể lấy dấu vết ngăn xếp của việc tạo và sử dụng nó làm tên luồng. Điều này có vẻ kỳ lạ nhưng tùy thuộc vào cách mã của bạn được cấu trúc, nó có thể rất hữu ích cho mục đích gỡ lỗi.

Điều này có vẻ như là một việc nhỏ nhưng khi bạn có một ứng dụng rất phức tạp với rất nhiều luồng và tất cả những điều bất ngờ 'đã dừng lại' (vì lý do bế tắc hoặc có thể do lỗ hổng trong giao thức mạng sẽ ít hơn hiển nhiên - hoặc các lý do vô tận khác) sau đó nhận được kết xuất ngăn xếp từ Java trong đó tất cả các luồng được gọi là 'Thread-1', 'Thread-2', 'Thread-3' không phải lúc nào cũng hữu ích (nó phụ thuộc vào cách các luồng của bạn có cấu trúc và liệu bạn có thể biết một cách hữu ích cái nào chỉ bằng dấu vết ngăn xếp của chúng - không phải lúc nào cũng có thể nếu bạn đang sử dụng các nhóm nhiều luồng tất cả chạy cùng một mã).

Phải nói rằng tất nhiên bạn cũng có thể thực hiện các thao tác trên theo cách chung bằng cách tạo một phần mở rộng của lớp luồng, đặt tên của nó thành dấu vết ngăn xếp của lệnh gọi tạo của nó và sau đó sử dụng nó với các triển khai Runnable của bạn thay vì lớp Thread java tiêu chuẩn (xem bên dưới) nhưng ngoài dấu vết ngăn xếp, có thể có thêm thông tin cụ thể theo ngữ cảnh sẽ hữu ích trong tên luồng để gỡ lỗi (tham chiếu đến một trong nhiều hàng đợi hoặc ổ cắm, ví dụ trong trường hợp bạn có thể thích mở rộng Chủ đề cụ thể cho trường hợp đó để bạn có thể yêu cầu trình biên dịch buộc bạn (hoặc những người khác sử dụng thư viện của bạn) chuyển thông tin nhất định (ví dụ: hàng đợi / ổ cắm trong câu hỏi) để sử dụng trong tên).

Đây là một ví dụ về luồng chung với dấu vết ngăn xếp gọi như tên của nó:

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"));
    }
}

và đây là một mẫu đầu ra so sánh hai tên:

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 quan điểm của bạn là gì? Bạn không thể sử dụng mã của mình ở trên trong quá trình thực thi luồng để có được một ngăn xếp của việc tạo luồng (thay vào đó bạn sẽ có được một tên đơn giản hoặc tốt nhất là một ngăn xếp của các luồng khởi chạy) nhưng bằng cách phân lớp luồng, bạn có thể thực hiện chính xác và buộc nó, thậm chí đòi hỏi thêm thông tin cụ thể theo ngữ cảnh do đó cung cấp cho bạn một sự hiểu biết cụ thể hơn về chính xác chủ đề nào có thể có vấn đề.
AntonyM

8
Quan điểm của tôi là "Nếu bạn triển khai Runnable thì lớp thực hiện Runnable không có quyền kiểm soát đối với tên luồng ..." là hoàn toàn sai. Một lớp triển khai thực Runnablesự có thể kiểm soát tên luồng, vì luồng chạy mã theo định nghĩa luồng hiện tại (và bất kỳ mã nào vượt qua kiểm tra bảo mật đều có quyền kiểm soát tên luồng). Xem xét bạn dành một nửa bài đăng của bạn cho "omg, những gì về tên chủ đề!", Điều đó có vẻ như là một vấn đề lớn.
cHao

Tên chủ đề? Không có gì ngăn cản bạn mở rộng lớp chủ đề là tốt.
RichieHH

11

Có thể chạy được vì:

  • Để linh hoạt hơn cho việc triển khai Runnable để mở rộng một lớp khác
  • Tách mã khỏi thực thi
  • Cho phép bạn chạy runnable của bạn từ một nhóm chủ đề, chuỗi sự kiện hoặc theo bất kỳ cách nào khác trong tương lai.

Ngay cả khi bạn không cần bất kỳ thứ gì trong số này bây giờ, bạn có thể trong tương lai. Vì không có lợi ích gì khi ghi đè Thread, Runnable là một giải pháp tốt hơn.


11

Vì đây là một chủ đề rất phổ biến và các câu trả lời hay được lan truyền khắp nơi và được xử lý rất sâu sắc, tôi cảm thấy thật hợp lý khi tổng hợp các câu trả lời hay từ những người khác thành một hình thức ngắn gọn hơn, vì vậy những người mới đến có một cái nhìn tổng quan dễ dàng:

  1. Bạn thường mở rộng một lớp để thêm hoặc sửa đổi chức năng. Vì vậy, nếu bạn không muốn để ghi đè lên bất kỳ hành vi Chủ đề , sau đó sử dụng Runnable.

  2. Trong ánh sáng đó, nếu bạn không cần phải kế thừa phương pháp chủ đề, bạn có thể làm mà không có chi phí bằng cách sử dụng Runnable.

  3. Kế thừa duy nhất : Nếu bạn mở rộng Chủ đề, bạn không thể mở rộng từ bất kỳ lớp nào khác, vì vậy nếu đó là điều bạn cần làm, bạn phải sử dụng Runnable.

  4. Đó là thiết kế tốt logic miền tách biệt với phương tiện kỹ thuật, trong ý nghĩa đó nó là tốt hơn để có một nhiệm vụ Runnable cô lập của bạn nhiệm vụ từ bạn Á hậu .

  5. Bạn có thể thực thi cùng một đối tượng Runnable nhiều lần , một đối tượng Thread, tuy nhiên, chỉ có thể được khởi động một lần. (Có thể là lý do, tại sao Executor chấp nhận Runnables, nhưng không phải Chủ đề.)

  6. Nếu bạn phát triển nhiệm vụ của mình là Runnable, bạn có thể linh hoạt sử dụng nó ngay bây giờ và trong tương lai . Bạn có thể có nó chạy đồng thời thông qua Executors mà còn thông qua Thread. Và bạn vẫn có thể sử dụng / gọi nó không đồng thời trong cùng một luồng giống như bất kỳ loại / đối tượng thông thường khác.

  7. Điều này làm cho việc phân tách các khía cạnh logic và đồng thời trong các bài kiểm tra đơn vị của bạn cũng dễ dàng hơn .

  8. Nếu bạn quan tâm đến câu hỏi này, bạn cũng có thể quan tâm đến sự khác biệt giữa Callable và Runnable .


@Pino Vâng, chính nó cũng là một Runnable. Tuy nhiên, nếu bạn mở rộng nó để chỉ sử dụng nó như một Runnable, thì sao? Tại sao không sử dụng Runnable đơn giản mà không có tất cả hành lý. Vì vậy, tôi tranh luận rằng, nếu bạn mở rộng Thread, bạn cũng sẽ thực thi nó bằng cách sử dụng phương thức start của nó, chỉ có thể được sử dụng một lần. Đó là điểm mà Nidhish-Krishnan muốn đưa ra trong câu trả lời của mình. Lưu ý, cái của tôi chỉ là một bản tổng hợp hoặc tóm tắt ngắn gọn về các câu trả lời khác ở đây.
Jörg

11

Sự khác biệt giữa Mở rộng chủ đề và triển khai Runnable là:

nhập mô tả hình ảnh ở đây


8

Điều này được thảo luận trong Hướng dẫn xác định và bắt đầu một chủ đề của Oracle :

Những thành ngữ nào bạn nên sử dụng? Thành ngữ đầu tiên, sử dụng một đối tượng Runnable, là khái quát hơn, bởi vì đối tượng Runnable có thể phân lớp một lớp khác ngoài Thread. Thành ngữ thứ hai dễ sử dụng hơn trong các ứng dụng đơn giản, nhưng bị giới hạn bởi thực tế là lớp nhiệm vụ của bạn phải là hậu duệ của Thread. Bài học này tập trung vào cách tiếp cận đầu tiên, phân tách tác vụ Runnable khỏi đối tượng Thread thực thi tác vụ. Cách tiếp cận này không chỉ linh hoạt hơn mà còn có thể áp dụng cho các API quản lý luồng cấp cao được đề cập sau này.

Nói cách khác, việc triển khai Runnablesẽ hoạt động trong các tình huống trong đó lớp của bạn mở rộng một lớp khác Thread. Java không hỗ trợ nhiều kế thừa. Ngoài ra, việc mở rộng Threadsẽ không thể thực hiện được khi sử dụng một số API quản lý luồng cấp cao. Kịch bản duy nhất mà việc mở rộng Threadđược ưu tiên là trong một ứng dụng nhỏ sẽ không phải cập nhật trong tương lai. Hầu như luôn luôn tốt hơn để thực hiện Runnablevì nó linh hoạt hơn khi dự án của bạn phát triển. Thay đổi thiết kế sẽ không có tác động lớn vì bạn có thể triển khai nhiều giao diện trong java, nhưng chỉ mở rộng một lớp.


8

Giải thích đơn giản nhất là bằng cách triển khai Runnablechúng ta có thể gán cùng một đối tượng cho nhiều luồng và mỗi luồngThread chia sẻ cùng một trạng thái và hành vi của đối tượng.

Ví dụ, giả sử có hai luồng, thread1 đặt một số nguyên trong một mảng và thread2 lấy số nguyên từ mảng khi mảng được lấp đầy. Lưu ý rằng để thread2 hoạt động, nó cần biết trạng thái của mảng, cho dù thread1 có lấp đầy nó hay không.

Việc triển khai Runnablecho phép bạn có sự linh hoạt này để chia sẻ đối tượng trong khi extends Threadkhiến bạn tạo các đối tượng mới cho mỗi luồng do đó mọi cập nhật được thực hiện bởi thread1 đều bị mất đối với thread2.


7

Nếu tôi không sai, nó ít nhiều giống với

Sự khác biệt giữa một giao diện và lớp trừu tượng là gì?

mở rộng thiết lập quan hệ & giao diện " Is A " cung cấp " Có một khả năng ".

Thích thực hiện Runnable :

  1. Nếu bạn không phải mở rộng lớp Thread và sửa đổi triển khai mặc định API Thread
  2. Nếu bạn đang thực hiện một lệnh cháy và quên
  3. Nếu bạn đã mở rộng một lớp khác

Thích " mở rộng chủ đề ":

  1. Nếu bạn phải ghi đè bất kỳ phương thức Thread nào như được liệt kê trong trang tài liệu oracle

Nói chung, bạn không cần ghi đè hành vi Chủ đề. Vì vậy, thực hiện Runnable được ưa thích trong hầu hết các lần.

Trên một lưu ý khác, sử dụng nâng cao ExecutorServicehoặc ThreadPoolExecutorServiceAPI cung cấp sự kiểm soát và linh hoạt hơn.

Có một cái nhìn về câu hỏi SE này:

ExecutorService vs Casual Thread Spawner


5

Việc tách lớp Thread khỏi triển khai Runnable cũng tránh được các vấn đề đồng bộ hóa tiềm ẩn giữa luồng và phương thức run (). Một Runnable riêng biệt thường mang lại sự linh hoạt cao hơn theo cách mà mã có thể chạy được tham chiếu và thực thi.


5

Đó là S của RẮN : Trách nhiệm duy nhất.

Một luồng thể hiện bối cảnh đang chạy (như trong bối cảnh thực hiện: khung stack, id luồng, v.v.) của việc thực thi không đồng bộ của một đoạn mã. Đó là đoạn mã lý tưởng nên được thực hiện tương tự, cho dù đồng bộ hoặc không đồng bộ .

Nếu bạn kết hợp chúng lại với nhau trong một lần thực hiện, bạn đưa ra đối tượng kết quả là hai nguyên nhân thay đổi không liên quan :

  1. xử lý luồng trong ứng dụng của bạn (ví dụ: truy vấn và sửa đổi bối cảnh thực hiện)
  2. thuật toán được thực thi bởi đoạn mã (phần có thể chạy được)

Nếu ngôn ngữ bạn sử dụng hỗ trợ các lớp một phần hoặc nhiều kế thừa, thì bạn có thể tách riêng từng nguyên nhân trong siêu lớp riêng của nó, nhưng ngôn ngữ này sẽ giống nhau khi kết hợp hai đối tượng, vì các bộ tính năng của chúng không trùng nhau. Đó là lý thuyết.

Trong thực tế, nói chung, một chương trình không cần phải mang nhiều phức tạp hơn mức cần thiết. Nếu bạn có một luồng làm việc trên một tác vụ cụ thể, mà không bao giờ thay đổi tác vụ đó, có lẽ không có điểm nào trong việc tạo các lớp riêng biệt và mã của bạn vẫn đơn giản hơn.

Trong ngữ cảnh của Java , vì cơ sở đã có sẵn , có lẽ dễ dàng hơn để bắt đầu trực tiếp với Runnablecác lớp độc lập và chuyển các cá thể của chúng sang Thread(hoặc Executor) cá thể. Khi đã quen với mẫu đó, nó không khó sử dụng (hoặc thậm chí đọc) hơn trường hợp luồng có thể chạy đơn giản.


5

Một lý do bạn muốn triển khai một giao diện thay vì mở rộng một lớp cơ sở là bạn đã mở rộng một số lớp khác. Bạn chỉ có thể mở rộng một lớp, nhưng bạn có thể thực hiện bất kỳ số lượng giao diện nào.

Nếu bạn mở rộng Thread, về cơ bản, bạn sẽ ngăn logic của bạn được thực thi bởi bất kỳ luồng nào khác ngoài 'this'. Nếu bạn chỉ muốn một số luồng thực thi logic của mình, tốt hơn là chỉ thực hiện Runnable.


Có bằng cách triển khai giao diện Runnable để tự do thực hiện logic của riêng bạn bằng cách mở rộng bất kỳ lớp nào, đó là lý do tại sao Runnable chủ yếu được ưa thích hơn lớp Thread.
Akash5288

5

nếu bạn sử dụng runnable, bạn có thể tiết kiệm không gian để mở rộng cho bất kỳ lớp nào khác.


5

Chúng ta có thể truy cập lại lý do cơ bản mà chúng ta muốn lớp chúng ta cư xử như một Thread ? Không có lý do nào cả, chúng tôi chỉ muốn thực hiện một tác vụ, rất có thể ở chế độ không đồng bộ, điều đó có nghĩa chính xác là việc thực thi tác vụ phải phân nhánh từ luồng chính của chúng tôi và luồng chính nếu kết thúc sớm, có thể hoặc không thể chờ đợi cho đường dẫn phân nhánh (nhiệm vụ).

Nếu đây là toàn bộ mục đích, thì tôi thấy sự cần thiết của một Chủ đề chuyên biệt. Điều này có thể được thực hiện bằng cách chọn một Chủ đề RAW từ Nhóm chủ đề của hệ thống và giao cho nó nhiệm vụ của chúng tôi (có thể là một thể hiện của lớp chúng tôi) và đó là nó.

Vì vậy, chúng ta hãy tuân theo khái niệm OOP và viết một lớp về loại mà chúng ta cần. Có nhiều cách để làm mọi thứ, làm nó đúng cách quan trọng.

Chúng ta cần một tác vụ, vì vậy hãy viết một định nghĩa nhiệm vụ có thể chạy trên một Thread. Vì vậy, sử dụng Runnable.

Luôn nhớ implementsđược sử dụng đặc biệt để truyền đạt một hành vi và extendsđược sử dụng để truyền đạt một tính năng / tài sản.

Chúng tôi không muốn thuộc tính của luồng, thay vào đó chúng tôi muốn lớp của chúng tôi hoạt động như một nhiệm vụ có thể được chạy.


4

Có, nếu bạn gọi cuộc gọi ThreadA, thì không cần gọi phương thức start và phương thức chạy là gọi sau khi chỉ gọi lớp ThreadA. Nhưng nếu sử dụng cuộc gọi ThreadB thì cần phải bắt đầu luồng cho phương thức chạy cuộc gọi. Nếu bạn có thêm trợ giúp, trả lời tôi.


4

Tôi thấy hữu ích nhất khi sử dụng Runnable vì tất cả các lý do được đề cập, nhưng đôi khi tôi muốn mở rộng Thread để tôi có thể tạo phương thức dừng luồng của riêng mình và gọi trực tiếp trên luồng mà tôi đã tạo.


4

Java không hỗ trợ nhiều kế thừa, vì vậy nếu bạn mở rộng lớp Thread thì sẽ không có lớp nào khác được mở rộng.

Ví dụ: Nếu bạn tạo một applet thì nó phải mở rộng lớp Applet vì vậy ở đây cách duy nhất để tạo luồng là bằng cách thực hiện giao diện Runnable


4

Runnablelà một giao diện, trong khi đó Threadlà một lớp thực hiện giao diện này. Từ quan điểm thiết kế, cần có một sự tách biệt rõ ràng giữa cách xác định một nhiệm vụ và giữa cách thức thực hiện nó. Cái trước là trách nhiệm của một Runnalbethực hiện, và cái sau là công việc của Threadlớp. Trong hầu hết các trường hợp thực hiện Runnablelà cách đúng đắn để làm theo.


4

Sự khác biệt giữa Thread và runnable. Nếu chúng ta đang tạo Thread bằng lớp Thread thì Số luồng bằng số lượng đối tượng chúng ta đã tạo. Nếu chúng ta đang tạo luồng bằng cách triển khai giao diện có thể chạy được thì chúng ta có thể sử dụng một đối tượng để tạo nhiều luồng. Vì vậy, một đối tượng được chia sẻ bởi nhiều luồng. Vì vậy, nó sẽ tốn ít bộ nhớ hơn

Vì vậy, tùy thuộc vào yêu cầu nếu dữ liệu của chúng tôi không senstive. Vì vậy, nó có thể được chia sẻ giữa nhiều Thread chúng ta có thể sử dụng giao diện Runnable.


4

Thêm hai xu của tôi ở đây - Luôn luôn sử dụng bất cứ khi nào có thể implements Runnable. Dưới đây là hai lời cảnh báo về lý do tại sao bạn không nên sử dụng extends Threads

  1. Lý tưởng nhất là bạn không bao giờ nên mở rộng lớp Thread; các Threadlớp nên được thực hiện final. Ít nhất là phương pháp của nó như thế nào thread.getId(). Xem cuộc thảo luận này cho một lỗi liên quan đến việc mở rộng Threads.

  2. Những người thích giải câu đố có thể thấy một tác dụng phụ khác của việc mở rộng Thread. Mã dưới đây sẽ in mã không thể truy cập khi không ai thông báo cho họ.

Vui lòng xem 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 ");
        }
    } 
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.