“ ใช้ 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 ก่อนที่จะมีอยู่ดังนั้นและมีข้อมูลที่ผิด / ล้าสมัยจำนวนมากอยู่ที่นั่น
James McMahon

5
มีเหตุผลหนึ่งที่คุณอาจต้องการขยายเธรด ( แต่ฉันไม่แนะนำ ) คุณสามารถจัดการinterrupt()ได้ อีกครั้งมันเป็นความคิดมันอาจจะเป็นประโยชน์ในกรณีที่ถูกต้อง แต่ฉันไม่แนะนำ
bestsss

โปรดดูคำตอบที่อธิบายไว้เป็นอย่างดี: stackoverflow.com/q/5562720/285594

@ bestss ฉันพยายามไขความหมายของการจัดการขัดจังหวะ () คุณพยายามที่จะแทนที่วิธีการหรือไม่
Bob Cross

8
ใช่ตามรหัสแล้วคลาสเธรด A สามารถขยายคลาสใดก็ได้ในขณะที่คลาสเธรด B ไม่สามารถขยายคลาสอื่น ๆ ได้
mani deepak

คำตอบ:


1672

ใช่: การดำเนินการRunnableเป็นวิธีที่ต้องการในการทำเช่นนั้น IMO คุณไม่ได้เชี่ยวชาญพฤติกรรมของเธรดจริงๆ คุณแค่ให้มันทำงาน นั่นหมายความว่าองค์ประกอบคือปรัชญา "บริสุทธิ์" วิธีที่จะไป

ในทางปฏิบัติแง่การก็หมายความว่าคุณสามารถใช้Runnableและขยายจากชั้นเรียนอื่นเช่นกัน


151
ตรงไปตรงมาใส่ พฤติกรรมใดที่เราพยายามเขียนทับในเธรดโดยขยายออกไป ฉันจะยืนยันว่าคนส่วนใหญ่ไม่ได้พยายามเขียนทับพฤติกรรมใด ๆ แต่พยายามใช้พฤติกรรมของเธรด
hooknc

87
ในฐานะที่เป็นความคิดเห็นด้านข้างหากคุณสร้างอินสแตนซ์ของเธรดและไม่เรียกใช้เมธอด start () คุณกำลังสร้างหน่วยความจำรั่วใน Java <5 (ไม่เกิดขึ้นกับ Runnables): stackoverflow.com/questions/107823/ …
Nacho Coloma

25
ข้อดีอย่างหนึ่งของ Runnable คือถ้าในบางกรณีคุณไม่สนใจหรือไม่ต้องการใช้เธรดและคุณต้องการรันโค้ดคุณมีตัวเลือกที่จะเรียกใช้ run () เช่น (handwavy มาก) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
user949300

10
@ user949300 คุณสามารถทำเช่นนั้นด้วยextends Threadและถ้าคุณไม่ต้องการเธรดทำไมคุณถึงต้องติดตั้งRunnable...
m0skit0

30
ในการถอดความ Sierra และ Bates ประโยชน์หลักของการใช้ Runnable คือคุณกำลังแยก "งาน" ทางสถาปัตยกรรมออกจาก "นักวิ่ง"
8bitjunkie

569

tl; dr: ใช้ Runnable ดีกว่า อย่างไรก็ตามข้อแม้มีความสำคัญ

โดยทั่วไปฉันขอแนะนำให้ใช้สิ่งที่ชอบRunnableมากกว่าThreadเพราะจะช่วยให้คุณทำงานของคุณควบคู่ไปกับการเลือกการทำงานพร้อมกันอย่างหลวม ๆ ตัวอย่างเช่นหากคุณใช้Runnableและตัดสินใจในภายหลังว่าสิ่งนี้ไม่จำเป็นต้องเป็นของจริงThreadคุณสามารถโทรไปที่ threadA.run ()

Caveat:ที่นี่ฉันไม่แนะนำให้ใช้หัวข้อดิบ ฉันชอบการใช้งานของCallablesและFutureTasks (จาก javadoc: "การคำนวณแบบอะซิงโครนัสที่ยกเลิกได้") การรวมการหมดเวลาการยกเลิกที่เหมาะสมและการรวมเธรดของการสนับสนุนการเกิดพร้อมกันที่ทันสมัยมีประโยชน์กับฉันมากกว่ากองดิบหัวข้อ

การติดตามผล:มีคอน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
นี่เป็นคำตอบที่ดีกว่า IMHO สิ่งหนึ่ง: ตัวอย่างรหัสที่คุณไม่ได้ปิดผู้บริหารและฉันเห็นคำถามนับล้านที่ผู้คนทำผิดนี้สร้างผู้บริหารใหม่ทุกครั้งที่พวกเขาต้องการวางไข่ esจะดีกว่าเป็นฟิลด์คงที่ (หรือฉีด) ดังนั้นมันจะถูกสร้างขึ้นเพียงครั้งเดียว
artbristol

7
@artbristol ขอบคุณ! ฉันไม่เห็นด้วยกับผู้บริหารใหม่ (เราทำสิ่งที่คุณแนะนำในรหัสของเรา) ในการเขียนคำตอบดั้งเดิมฉันพยายามเขียนรหัสน้อยที่สุดลงในแฟรกเมนต์ต้นฉบับ เราต้องหวังว่าผู้อ่านหลายคนของคำตอบเหล่านี้ใช้พวกเขาเป็นจุดกระโดดออก ฉันไม่ได้พยายามเขียนแทน javadoc ฉันเขียนเอกสารทางการตลาดอย่างมีประสิทธิภาพถ้าคุณชอบวิธีนี้คุณควรเห็นสิ่งที่ยอดเยี่ยมอื่น ๆ ที่เรามีให้ ...
Bob Cross

3
ฉันรู้ว่าฉันค่อนข้างช้าแสดงความคิดเห็นในเรื่องนี้ แต่FutureTaskโดยทั่วไปแล้วการติดต่อโดยตรงไม่ใช่สิ่งที่คุณต้องการจะทำ ExecutorServices จะสร้างที่เหมาะสมFutureสำหรับคุณเมื่อคุณ/ ให้กับพวกเขา ในทำนองเดียวกันสำหรับและเมื่อคุณ/ submitRunnableCallableScheduledExecutorServiceScheduledFuturescheduleRunnableCallable
Powerlord

3
@ Powerlord ความตั้งใจของฉันคือการสร้างรหัสเศษเล็กเศษน้อยที่ตรงกับของ OP ให้ใกล้เคียงที่สุด ฉันยอมรับว่า FutureTask ใหม่นั้นไม่เหมาะสม แต่ชัดเจนสำหรับวัตถุประสงค์ในการอธิบาย
Bob Cross

257

นิทานสอนใจ:

รับเฉพาะเมื่อคุณต้องการแทนที่พฤติกรรมบางอย่าง

หรือควรอ่านว่า:

สืบทอดน้อยกว่าอินเตอร์เฟสมากขึ้น


1
นี่ควรเป็นคำถามเสมอหากคุณเริ่มสร้าง Object ที่ทำงานพร้อมกัน! คุณต้องการฟังก์ชั่น Thread Object หรือไม่?
Liebertee

4
เมื่อสืบทอดจากเธรดหนึ่งมักต้องการแทนที่การทำงานของrun()เมธอด
Warren Dew

1
คุณไม่สามารถแทนที่พฤติกรรมของ a java.lang.Threadโดยการแทนที่run()เมธอด ในกรณีนี้คุณต้องแทนที่start()วิธีที่ฉันเดา โดยปกติคุณเพียงแค่นำพฤติกรรมการใช้งานกลับมาใช้ใหม่java.lang.Threadโดยการฉีดบล็อกการดำเนินการของคุณเข้ากับrun()วิธีการ
sura2k

การสืบทอดไม่ได้เป็นเพียงการเอาชนะพฤติกรรมบางอย่างเท่านั้น แต่ยังใช้พฤติกรรมทั่วไปด้วย และมันก็เป็นสิ่งที่ตรงกันข้ามยิ่งมีความสำคัญมากกว่าลำดับชั้นยิ่งแย่ลง
peja

232

มีคำตอบที่ดีมากมายฉันต้องการเพิ่มมากขึ้น Extending v/s Implementing Threadนี้จะช่วยให้เข้าใจ
ขยายการผูกสองคลาสไฟล์อย่างใกล้ชิดและอาจทำให้บางอย่างยากที่จะจัดการกับรหัส

ทั้งสองวิธีทำงานแบบเดียวกัน แต่มีความแตกต่างกันบ้าง
ความแตกต่างที่พบบ่อยที่สุดคือ

  1. เมื่อคุณขยายคลาสเธรดหลังจากนั้นคุณจะไม่สามารถขยายคลาสอื่นที่คุณต้องการ (ดังที่คุณทราบ Java ไม่อนุญาตให้สืบทอดมากกว่าหนึ่งคลาส)
  2. เมื่อคุณใช้ Runnable คุณสามารถประหยัดพื้นที่สำหรับคลาสของคุณเพื่อขยายคลาสอื่น ๆ ในอนาคตหรือตอนนี้

อย่างไรก็ตามความแตกต่างที่สำคัญอย่างหนึ่งระหว่างการใช้ Runnable และการขยายเธรดคือ
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 เมื่อใด
ใช้อินเตอร์เฟสที่รันได้เมื่อคุณต้องการเข้าถึงทรัพยากรเดียวกันจากกลุ่มเธรด หลีกเลี่ยงการใช้คลาสของเธรดที่นี่เนื่องจากการสร้างวัตถุหลายรายการใช้หน่วยความจำมากขึ้นและจะกลายเป็นค่าใช้จ่ายที่มีประสิทธิภาพมาก

คลาสที่ใช้ Runnable ไม่ใช่เธรดและเป็นคลาส เพื่อให้ Runnable เป็นเธรดคุณต้องสร้างอินสแตนซ์ของเธรดและส่งตัวเองเป็นเป้าหมาย

ในกรณีส่วนใหญ่ควรใช้อินเทอร์เฟซที่เรียกใช้ได้หากคุณวางแผนที่จะแทนที่เท่านั้น run()เมธอดเท่านั้นและไม่มีเมธอดเธรดอื่น ๆ สิ่งนี้มีความสำคัญเนื่องจากคลาสไม่ควรเป็นคลาสย่อยยกเว้นว่าโปรแกรมเมอร์ตั้งใจที่จะแก้ไขหรือปรับปรุงพฤติกรรมพื้นฐานของคลาส

เมื่อมีความจำเป็นต้องขยายซูเปอร์คลาสการใช้อินเตอร์เฟส Runnable เหมาะสมกว่าการใช้คลาสเธรด เนื่องจากเราสามารถขยายคลาสอื่นได้ในขณะที่ใช้ส่วนติดต่อแบบ Runnable เพื่อสร้างเธรด

ฉันหวังว่านี่จะช่วยได้!


40
รหัสของคุณผิดปกติ ฉันหมายความว่ามันทำในสิ่งที่มันทำ แต่ไม่ใช่สิ่งที่คุณตั้งใจจะแสดง
zEro

38
ในการชี้แจง: สำหรับกรณีที่ runnable คุณได้ใช้อินสแตนซ์ ImplementsRunnable เดียวกันเพื่อเริ่มหลายเธรดในขณะที่สำหรับกรณีของเธรดที่คุณสร้างอินสแตนซ์ 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นั้นผิดหรือเปล่า? ถ้าไม่เช่นนั้นจะเกิดอะไรขึ้นกับสิ่งนี้?
เครื่องซักผ้า Evil

4
รหัสที่โพสต์ที่นี่ทำให้เข้าใจผิดและอาจทำให้ปวดหัว ขอบคุณ @zEro สำหรับการล้างข้อมูลเล็กน้อย
Nikos

78

สิ่งหนึ่งที่ฉันประหลาดใจยังไม่ได้กล่าวถึงคือการใช้Runnableทำให้ชั้นเรียนของคุณมีความยืดหยุ่นมากขึ้น

หากคุณขยายเธรดการกระทำที่คุณทำจะเป็นไปในเธรดเสมอ อย่างไรก็ตามถ้าคุณใช้Runnableมันไม่จำเป็นต้องเป็น คุณสามารถเรียกใช้งานในเธรดหรือส่งผ่านไปยังบริการเอ็กซีคิวต์บางประเภทหรือเพียงแค่ส่งผ่านไปเป็นงานภายในแอปพลิเคชั่นเธรดเดียว (อาจถูกเรียกใช้ในภายหลัง แต่ภายในเธรดเดียวกัน) ตัวเลือกที่มีจำนวนมากที่เปิดกว้างมากขึ้นถ้าคุณเพียงแค่ใช้กว่าถ้าคุณผูกตัวเองไปRunnableThread


6
คุณสามารถทำสิ่งเดียวกันกับThreadวัตถุได้จริง ๆเพราะThread implements Runnable… ;-) แต่มันรู้สึกดีขึ้นที่ทำสิ่งนี้ด้วยการRunnableทำสิ่งเหล่านั้นด้วยThread!
siegi

7
จริง แต่Threadเพิ่มสิ่งพิเศษมากมายที่คุณไม่ต้องการและในหลายกรณีไม่ต้องการ คุณดีกว่าเสมอในการใช้อินเทอร์เฟซที่ตรงกับสิ่งที่คุณกำลังทำอยู่
Herms

74

ถ้าคุณต้องการที่จะนำไปปฏิบัติหรือขยายคลาสอื่น ๆRunnableอินเทอร์เฟซนั้นจะเป็นที่นิยมมากที่สุดมิฉะนั้นถ้าคุณไม่ต้องการให้คลาสอื่นขยายหรือนำThreadคลาสไปใช้

ความแตกต่างที่พบบ่อยที่สุดคือ

ป้อนคำอธิบายรูปภาพที่นี่

เมื่อคุณextends Threadเรียนหลังจากนั้นคุณจะไม่สามารถขยายชั้นเรียนอื่นที่คุณต้องการ (ดังที่คุณทราบ Java ไม่อนุญาตให้สืบทอดมากกว่าหนึ่งคลาส)

เมื่อคุณimplements Runnableคุณสามารถประหยัดพื้นที่สำหรับชั้นเรียนของคุณเพื่อขยายชั้นเรียนอื่น ๆ ในอนาคตหรือตอนนี้

  • Java ไม่รองรับการสืบทอดหลายอย่างซึ่งหมายความว่าคุณสามารถขยายได้เพียงหนึ่งคลาสใน Java ดังนั้นเมื่อคุณขยายเธรดคลาสคุณจะเสียโอกาสและไม่สามารถขยายหรือสืบทอดคลาสอื่นใน Java ได้

  • ในการเขียนโปรแกรมเชิงวัตถุการขยายคลาสโดยทั่วไปหมายถึงการเพิ่มฟังก์ชันการทำงานใหม่และการแก้ไขหรือปรับปรุงพฤติกรรม หากเราไม่ได้ทำการแก้ไขใด ๆ บน Thread ให้ใช้ส่วนติดต่อที่เรียกใช้แทนได้

  • ส่วนต่อประสานที่รันได้หมายถึงงานที่สามารถดำเนินการได้โดยเธรดธรรมดาหรือผู้บริหารหรือวิธีการอื่นใด ดังนั้นการแยกลอจิคัลของภารกิจในรูปแบบ Runnable มากกว่า Thread จึงเป็นการตัดสินใจที่ดี

  • การแยกงานออกเป็น Runnable หมายความว่าเราสามารถนำภารกิจนั้นกลับมาใช้ใหม่และยังมีอิสระในการดำเนินงานจากวิธีการที่แตกต่างกัน เนื่องจากคุณไม่สามารถเริ่มหัวข้อใหม่ได้เมื่อดำเนินการเสร็จสิ้น เธรด Runnable อีกครั้งสำหรับภารกิจ Runnable เป็นผู้ชนะ

  • ผู้ออกแบบจาวาตระหนักถึงสิ่งนี้และนั่นเป็นสาเหตุที่ผู้บริหารยอมรับ Runnable เป็นงานและพวกเขามีเธรดผู้ปฏิบัติงานซึ่งดำเนินงานเหล่านั้น

  • การสืบทอดเมธอดเธรดทั้งหมดเป็นค่าใช้จ่ายเพิ่มเติมเพียงเพื่อแสดงถึงภารกิจที่สามารถทำได้อย่างง่ายดายด้วย Runnable

ความอนุเคราะห์จากjavarevisited.blogspot.com

สิ่งเหล่านี้มีความแตกต่างที่น่าสังเกตระหว่าง Thread และ Runnable ใน Java หากคุณทราบความแตกต่างอื่น ๆ ของ Thread vs Runnable มากกว่าโปรดแบ่งปันความคิดเห็น ฉันใช้ Runnable ผ่านเธรดสำหรับสถานการณ์นี้เป็นการส่วนตัวและแนะนำให้ใช้ Runnable หรือ Callable interface ตามความต้องการของคุณ

อย่างไรก็ตามความแตกต่างที่สำคัญคือ

เมื่อคุณextends Threadเรียนแต่ละเธรดของคุณจะสร้างวัตถุที่ไม่ซ้ำกันและเชื่อมโยงกับมัน เมื่อคุณimplements Runnableมันแชร์วัตถุเดียวกันกับหลายเธรด


73

ที่จริงแล้วไม่ควรเปรียบเทียบRunnableและThreadกัน

สองนี้มีการพึ่งพาและความสัมพันธ์ในหลายเธรดเหมือนกับWheel and Engineความสัมพันธ์ของยานยนต์

ฉันจะบอกว่ามีเพียงวิธีเดียวสำหรับมัลติเธรดที่มีสองขั้นตอนคือ ให้ฉันทำจุดของฉัน

Runnable:
เมื่อใช้งานinterface Runnableหมายความว่าคุณกำลังสร้างบางสิ่งที่อยู่run ableในเธรดอื่น ตอนนี้สร้างสิ่งที่สามารถเรียกใช้ภายในเธรด (เรียกใช้ภายในเธรด), ไม่ได้หมายถึงการสร้างเธรด
ดังนั้นคลาสMyRunnableจึงไม่มีอะไรนอกจากคลาสธรรมดาที่มีvoid runวิธีการ และมันก็เป็นวัตถุธรรมดาบางอย่างที่มีเพียงวิธีการrunที่จะดำเนินการตามปกติเมื่อถูกเรียก (ยกเว้นว่าเราส่งวัตถุในชุดข้อความ)

หัวข้อ:
class Threadฉันจะบอกว่าเป็นชั้นเรียนพิเศษมากที่มีความสามารถในการเริ่มหัวข้อใหม่ซึ่งจริง ๆ แล้วช่วยให้หลายเธรดผ่านstart()วิธีการของมัน

ทำไมไม่ฉลาดที่จะเปรียบเทียบ?
เพราะเราต้องการทั้งคู่สำหรับการทำเธรด

สำหรับมัลติเธรดเราต้องการสองสิ่ง:

  • สิ่งที่สามารถเรียกใช้ภายในเธรด (Runnable)
  • สิ่งที่สามารถเริ่มหัวข้อใหม่ (เธรด)

ดังนั้นในทางเทคนิคและในทางทฤษฎีแล้วทั้งคู่มีความจำเป็นที่จะต้องเริ่มการทำงานของเธรดหนึ่งจะทำงานและหนึ่งจะทำให้มันทำงาน (เช่นWheel and Engineยานยนต์)

นั่นเป็นเหตุผลที่คุณไม่สามารถเริ่มหัวข้อกับที่คุณต้องผ่านมันไปตัวอย่างของMyRunnableThread

แต่มันเป็นไปได้ที่จะสร้างและรันเธรดโดยใช้เพียงclass ThreadเพราะคลาสThreadดำเนินการRunnableเพื่อให้เราทุกคนรู้ว่าThreadยังเป็นRunnableภายใน

ในที่สุดThreadและRunnableจะเติมเต็มซึ่งกันและกันเพื่อ multithreading ไม่ได้เป็นคู่แข่งหรือเปลี่ยน


3
แน่นอน! นี่ควรเป็นคำตอบที่ยอมรับได้ BTW ฉันคิดว่าคำถามนี้ได้รับการแก้ไขแล้วและThreadAไม่มีความหมายอีกต่อไปแล้ว
idelvall

คำตอบที่ได้รับการยอมรับนั้นได้รับมอบอำนาจจากคุณมากกว่า @idelvall
Saif

คำตอบที่ดีที่สุด! ขอบคุณ!
MichaelYe

44

คุณควรใช้ Runnable แต่ถ้าคุณใช้งานบน Java 5 หรือสูงกว่าคุณไม่ควรเริ่มต้นด้วยnew Threadแต่ใช้ExecutorServiceแทน สำหรับรายละเอียดโปรดดูที่: วิธีการใช้เกลียวที่เรียบง่ายใน Java


5
ฉันจะไม่คิดว่า ExecutorService จะเป็นประโยชน์ถ้าคุณเพียงแค่ต้องการเปิดกระทู้เดียว
Powerlord

1
จากสิ่งที่ฉันได้เรียนรู้แล้วไม่ควรเริ่มเธรดด้วยตัวเองโดยทั่วไปอีกต่อไปเพราะการปล่อยให้บริการผู้ดำเนินการทำให้สามารถควบคุมได้มากขึ้น (เช่นรอเธรดที่จะระงับ) นอกจากนี้ฉันไม่เห็นอะไรเลยในคำถามที่บอกเป็นนัยว่าเป็นเรื่องเดียว
Fabian Steeg

4
จุดประสงค์ของการใช้มัลติเธรดคืออะไรหากเรารู้ว่า aprior ว่ามันจะเป็นเธรดเดี่ยว สมมุติว่าเรามีหลายกระทู้และคำตอบนี้มีค่า
zEro

1
@zEro ฉันค่อนข้างแน่ใจว่ามีเหตุผลที่มีเพียงเธรดการส่งเหตุการณ์เท่านั้น ฉันสงสัยว่ามันเป็นกรณีเดียวที่ดีที่สุดที่จะมีเธรดแยกต่างหาก แต่อาจไม่ดีที่สุดที่จะมีหลายเธรด
porcoesphino

33

ฉันไม่ใช่ผู้เชี่ยวชาญ แต่ฉันคิดว่ามีเหตุผลข้อเดียวที่จะใช้ Runnable แทนการขยาย Thread: Java รองรับการสืบทอดเพียงครั้งเดียวดังนั้นคุณจึงสามารถขยายได้เพียงหนึ่งคลาสเท่านั้น

แก้ไข: เดิมกล่าวว่า "การใช้อินเทอร์เฟซต้องใช้ทรัพยากรน้อยลง" เช่นกัน แต่คุณต้องสร้างอินสแตนซ์เธรดใหม่ด้วยวิธีใดวิธีนี้จึงเป็นสิ่งที่ผิด


ใน runnable เราไม่สามารถโทรผ่านเครือข่ายได้ใช่ไหม ในขณะที่ฉันกำลังมี android.os.NetworkOnMainThreadException แต่ด้วยการใช้เธรดฉันสามารถโทรผ่านเครือข่ายได้ โปรดแก้ไขฉันหากฉันผิด
Nabeel Thobani

@NabeelThobani Normal Java ไม่สนใจ แต่ดูเหมือนว่า Android จะทำ แต่ฉันไม่คุ้นเคยกับ Android ที่จะพูด
Powerlord

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 เพียงทำโดยไม่ระบุชื่อ
Don Roby

2
@ Don Roby: ซึ่งแตกต่างกัน บ่อยครั้งที่สะดวกและคุณสามารถใช้ฟิลด์และตัวแปรโลคัลสุดท้ายจากคลาส / เมธอดที่มี
Bart van Heukelom

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

แต่ถ้าคุณขยายเธรดคุณจะต้องจัดการสิ่งนี้ภายในคลาสเอง (เช่นเดียวกับในตัวอย่างของคุณคุณตั้งชื่อเธรด 'ThreadB') ในกรณีนี้คุณ:

A) อาจให้ชื่อที่มีประโยชน์มากขึ้นสำหรับการดีบัก

B) กำลังบังคับให้ชื่อนั้นใช้สำหรับทุกอินสแตนซ์ของคลาสนั้น (เว้นแต่คุณจะเพิกเฉยต่อความจริงที่ว่ามันเป็นเธรดและทำข้างบนด้วยราวกับว่าเป็น Runnable แต่เรากำลังพูดถึงการประชุมที่นี่ในกรณีใด ๆ ไม่สนใจความเป็นไปได้ที่ฉันรู้สึก)

ตัวอย่างเช่นคุณอาจใช้การติดตามสแต็กของการสร้างและใช้เป็นชื่อเธรด สิ่งนี้อาจดูแปลก แต่ขึ้นอยู่กับว่าโค้ดของคุณมีโครงสร้างแบบใดมีประโยชน์มากสำหรับการดีบัก

สิ่งนี้อาจดูเหมือนเล็ก แต่ที่คุณมีแอปพลิเคชันที่ซับซ้อนมากที่มีเธรดจำนวนมากและสิ่งที่ 'หยุด' ในทันใดทั้งหมด (ด้วยเหตุผลของการหยุดชะงักหรืออาจเป็นเพราะข้อบกพร่องในโปรโตคอลเครือข่ายซึ่งจะน้อยกว่า ชัดเจน - หรือเหตุผลที่ไม่มีที่สิ้นสุดอื่น ๆ ) จากนั้นรับสแต็กการถ่ายโอนข้อมูลจาก Java โดยที่เธรดทั้งหมดเรียกว่า 'Thread-1', 'Thread-2', 'Thread-3' ไม่ได้มีประโยชน์เสมอไป (ขึ้นอยู่กับว่าเธรดของคุณเป็นอย่างไร มีโครงสร้างและไม่ว่าคุณจะสามารถบอกได้ว่ามีประโยชน์เพียงใดซึ่งเป็นเพียงแค่การติดตามสแต็กของพวกเขา - เป็นไปไม่ได้เสมอไปถ้าคุณใช้กลุ่มของหลายเธรดทั้งหมดที่ใช้รหัสเดียวกัน)

ต้องบอกว่าคุณสามารถทำข้างต้นได้ด้วยวิธีทั่วไปโดยการสร้างส่วนขยายของคลาสเธรดซึ่งตั้งชื่อให้เป็นสแต็กการติดตามของการเรียกใช้การสร้างแล้วใช้กับการใช้งาน Runnable ของคุณแทนคลาส Java Thread มาตรฐาน (ดูด้านล่าง) แต่นอกเหนือจากการติดตามสแต็กอาจมีข้อมูลเฉพาะบริบทเพิ่มเติมที่จะเป็นประโยชน์ในชื่อเธรดสำหรับการดีบัก (การอ้างอิงถึงหนึ่งในหลาย ๆ คิวหรือซ็อกเก็ตมันสามารถประมวลผลตัวอย่างเช่นในกรณีที่คุณอาจต้องการ ขยาย 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 ไม่มีการควบคุมชื่อเธรด ... " เป็นเท็จ patently การใช้คลาสRunnableสามารถควบคุมชื่อเธรดได้อย่างแน่นอนเนื่องจากเธรดที่รันโค้ดคือโดยการกำหนดเธรดปัจจุบัน (และรหัสใด ๆ ที่ผ่านการตรวจสอบความปลอดภัยจะมีการควบคุมชื่อเธรด) พิจารณาว่าคุณอุทิศโพสต์ของคุณครึ่งหนึ่งเป็น "อืมแล้วชื่อกระทู้ล่ะ!" ซึ่งดูเหมือนจะเป็นเรื่องใหญ่
cHao

ชื่อเธรด ไม่มีอะไรจะหยุดคุณขยายชั้นด้ายเช่นกัน
RichieHH

11

เรียกใช้เพราะ:

  • ทำให้มีความยืดหยุ่นมากขึ้นสำหรับการใช้ Runnable เพื่อขยายคลาสอื่น
  • แยกรหัสจากการดำเนินการ
  • ช่วยให้คุณเรียกใช้ runnable ของคุณจากกลุ่มเธรดเธรดเหตุการณ์หรือด้วยวิธีอื่นใดในอนาคต

แม้ว่าคุณจะไม่ต้องการสิ่งใดในตอนนี้คุณก็สามารถทำได้ในอนาคต เนื่องจากไม่มีประโยชน์ที่จะเอาชนะเธรด Runnable จึงเป็นทางออกที่ดีกว่า


11

เนื่องจากนี่เป็นหัวข้อที่ได้รับความนิยมมากและคำตอบที่ดีก็แพร่กระจายไปทั่วและจัดการอย่างละเอียดถี่ถ้วนฉันรู้สึกว่ามันสมเหตุสมผลที่จะรวบรวมคำตอบที่ดีจากคนอื่น ๆ ในรูปแบบที่กระชับมากขึ้น

  1. โดยปกติคุณจะขยายคลาสเพื่อเพิ่มหรือแก้ไขฟังก์ชันการทำงาน ดังนั้นถ้าคุณไม่ต้องการที่จะเขียนทับใด ๆพฤติกรรมกระทู้แล้วใช้ Runnable

  2. ในแง่เดียวกันถ้าคุณไม่จำเป็นที่จะสืบทอดวิธีด้ายที่คุณสามารถทำได้โดยไม่ต้องว่าค่าใช้จ่ายโดยใช้ Runnable

  3. การสืบทอดเดี่ยว : ถ้าคุณขยายเธรดคุณไม่สามารถขยายจากคลาสอื่น ๆ ได้ดังนั้นถ้าเป็นสิ่งที่คุณต้องทำคุณต้องใช้ Runnable

  4. มันคือการออกแบบที่ดีที่จะตรรกะโดเมนที่แยกต่างหากจากวิธีการทางเทคนิคในความรู้สึกว่ามันเป็นการดีที่จะมีงาน Runnable แยกของงานจากคุณวิ่ง

  5. คุณสามารถรัน Runnable เดียวกันได้วัตถุหลายครั้งวัตถุ Thread แต่สามารถเริ่มได้เพียงครั้งเดียว (อาจเป็นเหตุผลทำไมผู้บริหารถึงยอมรับ Runnables แต่ไม่ใช่ Threads)

  6. หากคุณพัฒนางานเป็น Runnable คุณมีความยืดหยุ่นทั้งหมดในการใช้งานตอนนี้และในอนาคตความยืดหยุ่นในทุกวิธีการใช้งานในขณะนี้และในอนาคตคุณสามารถให้มันทำงานพร้อมกันผ่านทางผู้บริหาร แต่ยังผ่านทางกระทู้ และคุณยังสามารถใช้ / เรียกมันว่าไม่พร้อมกันภายในเธรดเดียวกันเหมือนกับชนิด / วัตถุทั่วไปอื่น ๆ

  7. สิ่งนี้ทำให้ง่ายต่อการแยกลักษณะของงานตรรกะและการทำงานพร้อมกันในการทดสอบหน่วยของคุณทดสอบหน่วย

  8. หากคุณมีความสนใจในคำถามนี้คุณอาจจะสนใจยังอยู่ในความแตกต่างระหว่าง Callable และ Runnable


@Pino ใช่แล้ว Thread เองก็เป็น Runnable อย่างไรก็ตามหากคุณขยายออกไปเพื่อใช้เป็น Runnable ประเด็นคืออะไร ทำไมไม่ใช้แค่ Runnable ธรรมดาโดยไม่มีสัมภาระทั้งหมด ดังนั้นฉันขอยืนยันว่าถ้าคุณขยาย Thread คุณจะต้องรันมันโดยใช้วิธีการเริ่มต้นของมันซึ่งสามารถใช้ได้เพียงครั้งเดียว นั่นคือประเด็นที่นิเดชิช - กฤษนันต้องการจะทำในคำตอบของเขา โปรดทราบว่าฉันเป็นเพียงการรวบรวมหรือสรุปสั้น ๆ ของคำตอบอื่น ๆ ที่นี่
Jörg


8

สิ่งนี้ถูกกล่าวถึงในบทช่วยสอนการกำหนดและการเริ่มเธรดของ Oracle :

คุณควรใช้สำนวนแบบใด สำนวนแรกซึ่งใช้วัตถุ Runnable นั้นเป็นเรื่องทั่วไปมากขึ้นเพราะวัตถุ Runnable สามารถคลาสย่อยอื่นที่ไม่ใช่ Thread สำนวนที่สองนั้นง่ายต่อการใช้งานในแอพพลิเคชั่นที่เรียบง่าย แต่ถูก จำกัด ด้วยความจริงที่ว่าคลาสงานของคุณจะต้องเป็นลูกหลานของเธรด บทเรียนนี้มุ่งเน้นวิธีแรกซึ่งแยกภารกิจ Runnable ออกจากวัตถุเธรดที่ดำเนินงาน วิธีนี้ไม่เพียงมีความยืดหยุ่นมากขึ้น แต่ยังใช้ได้กับ API การจัดการเธรดระดับสูงที่ครอบคลุมในภายหลัง

กล่าวอีกนัยหนึ่งการใช้งานRunnableจะทำงานในสถานการณ์ที่คลาสของคุณขยายคลาสอื่นนอกเหนือจากThreadนี้ Java ไม่รองรับการสืบทอดหลายรายการ นอกจากนี้การขยายThreadจะไม่สามารถทำได้เมื่อใช้ API การจัดการเธรดระดับสูงบางส่วน สถานการณ์เดียวที่Threadเป็นที่นิยมคือการขยายในแอปพลิเคชันขนาดเล็กที่จะไม่ถูกอัปเดตในอนาคต เป็นการดีกว่าที่จะดำเนินการRunnableตามความยืดหยุ่นมากขึ้นเมื่อโครงการของคุณเติบโตขึ้น การเปลี่ยนแปลงการออกแบบจะไม่ส่งผลกระทบอย่างมีนัยสำคัญเนื่องจากคุณสามารถใช้อินเทอร์เฟซจำนวนมากใน java ได้ แต่จะขยายได้เพียงคลาสเดียวเท่านั้น


8

คำอธิบายที่ง่ายที่สุดคือการใช้งานRunnableเราสามารถกำหนดวัตถุเดียวกันให้กับหลายเธรดและแต่ละThreadแชร์สถานะและพฤติกรรมเดียวกัน

ตัวอย่างเช่นสมมติว่ามีสองเธรดthread1วางจำนวนเต็มลงในอาร์เรย์และthread2ใช้จำนวนเต็มจากอาร์เรย์เมื่ออาร์เรย์เต็ม โปรดสังเกตว่าเพื่อให้thread2ทำงานได้นั้นจำเป็นต้องทราบสถานะของอาร์เรย์ว่าthread1เติมเต็มหรือไม่

การนำไปใช้งานRunnableช่วยให้คุณมีความยืดหยุ่นในการใช้วัตถุร่วมกันในขณะที่extends Threadทำให้คุณสามารถสร้างวัตถุใหม่สำหรับแต่ละเธรดดังนั้นการปรับปรุงใด ๆ ที่ทำโดย thread1 จะสูญหายไปที่ thread2


7

ถ้าฉันไม่ผิดมันคล้ายกันมากหรือน้อย

อะไรคือความแตกต่างระหว่างอินเตอร์เฟสและคลาสนามธรรม?

ขยายสร้างความสัมพันธ์" Is A " และอินเตอร์เฟสให้ความสามารถ " มี "

ชอบดำเนินการ Runnable :

  1. หากคุณไม่ต้องขยายคลาสเธรดและแก้ไขการใช้งานเธรด API เริ่มต้น
  2. หากคุณกำลังดำเนินการไฟและลืมคำสั่ง
  3. หากคุณกำลังขยายชั้นเรียนอื่นแล้ว

ชอบ " ขยายเธรด ":

  1. หากคุณต้องแทนที่วิธีการใด ๆ ของกระทู้เหล่านี้ตามที่ระบุไว้ในหน้าเอกสารของ oracle

โดยทั่วไปคุณไม่จำเป็นต้องแทนที่การทำงานของเธรด ดังนั้นใช้ Runnableเป็นส่วนใหญ่

ในบันทึกอื่นการใช้ขั้นสูงExecutorServiceหรือThreadPoolExecutorServiceAPI ให้ความยืดหยุ่นและการควบคุมที่มากขึ้น

ดูคำถาม SE นี้:

ExecutorService vs Casual Thread Spawner


5

การแยกคลาสเธรดออกจากการใช้งาน Runnable ยังช่วยหลีกเลี่ยงปัญหาการซิงโครไนซ์ระหว่างเธรดและเมธอด run () โดยทั่วไป Runnable แยกต่างหากจะให้ความยืดหยุ่นมากกว่าในการอ้างอิงและเรียกใช้โค้ดที่รันได้


5

นั่นคือSของSOLID : ความรับผิดชอบเดี่ยว

ด้ายคาดเดาบริบทการทำงาน (เช่นในบริบทของการดำเนินการ: กรอบกองด้าย id ฯลฯ ) ของการดำเนินการไม่ตรงกันของชิ้นส่วนของรหัส ว่าชิ้นส่วนของรหัสควรจะต้องดำเนินการเหมือนกันไม่ว่าจะเป็นซิงโครหรือไม่ตรงกัน

หากคุณรวมพวกมันเข้าด้วยกันในการนำไปใช้ครั้งเดียวคุณจะให้สาเหตุของการเปลี่ยนแปลงที่ไม่เกี่ยวข้องกับวัตถุสองอย่าง:

  1. การจัดการเธรดในแอปพลิเคชันของคุณ (เช่นการสอบถามและแก้ไขบริบทการดำเนินการ)
  2. อัลกอริทึมที่ใช้โดยชิ้นส่วนของรหัส (ส่วนที่รันได้)

หากภาษาที่คุณใช้สนับสนุนคลาสบางส่วนหรือหลายมรดกคุณสามารถแยกแต่ละสาเหตุในระดับซูเปอร์ของตัวเอง แต่มันเดือดลงไปเช่นเดียวกับการเขียนวัตถุทั้งสองเนื่องจากชุดคุณลักษณะของพวกเขาไม่ทับซ้อนกัน นั่นคือทฤษฎี

ในทางปฏิบัติโดยทั่วไปแล้วโปรแกรมไม่จำเป็นต้องมีความซับซ้อนเกินความจำเป็น หากคุณมีหนึ่งเธรดที่ทำงานกับงานเฉพาะโดยไม่ต้องเปลี่ยนงานนั้นอาจไม่มีประเด็นในการทำให้งานแยกชั้นเรียนและรหัสของคุณยังคงง่ายกว่า

ในบริบทของJavaเนื่องจากสิ่งอำนวยความสะดวกมีอยู่แล้วมันอาจเป็นเรื่องง่ายที่จะเริ่มต้นโดยตรงด้วยRunnableคลาสแบบสแตนด์อะโลนและส่งต่ออินสแตนซ์ของพวกเขาไปยังThread(หรือExecutor) อินสแตนซ์ เมื่อคุ้นเคยกับรูปแบบดังกล่าวแล้วก็ไม่ยากที่จะใช้ (หรือแม้กระทั่งอ่าน) กว่าเคสเธรดที่รันได้แบบง่าย


5

เหตุผลหนึ่งที่คุณต้องการใช้อินเทอร์เฟซแทนการขยายคลาสพื้นฐานคือคุณกำลังขยายคลาสอื่นอยู่แล้ว คุณสามารถขยายได้หนึ่งคลาสเท่านั้น แต่คุณสามารถใช้อินเตอร์เฟสจำนวนเท่าใดก็ได้

หากคุณขยายเธรดคุณจะป้องกันไม่ให้ลอจิกของคุณดำเนินการโดยเธรดอื่นใดนอกเหนือจาก 'this' ถ้าคุณต้องการให้เธรดบางตัวรันตรรกะของคุณจะเป็นการดีกว่าถ้าใช้ Runnable


ใช่โดยการใช้ส่วนติดต่อแบบ Runnable เพื่อให้มีอิสระในการใช้ตรรกะของคุณเองโดยการขยายคลาสใด ๆ นั่นเป็นเหตุผลว่าทำไมส่วนใหญ่ของ Runnable จึงเป็นที่ต้องการมากกว่าคลาส Thread
Akash5288

5

ถ้าคุณใช้ runnable คุณสามารถประหยัดพื้นที่เพื่อขยายไปยังคลาสอื่น ๆ ของคุณ


5

เราสามารถเยี่ยมชมอีกครั้งเหตุผลพื้นฐานที่เราต้องการชั้นเรียนของเราที่จะประพฤติเป็นThread? ไม่มีเหตุผลเลยเราเพียงแค่ต้องการเรียกใช้งานซึ่งส่วนใหญ่จะเป็นในโหมดอะซิงโครนัสซึ่งหมายความว่าการเรียกใช้งานต้องแยกจากเธรดหลักของเราและเธรดหลักถ้าเสร็จก่อนอาจจะหรืออาจจะไม่รอ สำหรับเส้นทางแยก (งาน)

หากนี่คือจุดประสงค์ทั้งหมดฉันจะเห็นความต้องการของเธรดเฉพาะทางที่ไหน สิ่งนี้สามารถทำได้โดยการเลือกเธรด RAW จากเธรดพูลของระบบและกำหนดภารกิจของเรา (อาจเป็นตัวอย่างของคลาสของเรา) และนั่นคือมัน

ดังนั้นให้เราทำตามแนวคิดของ OOP และเขียนคลาสที่เราต้องการ มีหลายวิธีในการทำสิ่งต่าง ๆ ทำในสิ่งที่ถูกต้องสำคัญ

เราต้องการภารกิจดังนั้นให้เขียนคำนิยามภารกิจซึ่งสามารถเรียกใช้บนเธรดได้ ดังนั้นใช้ Runnable

โปรดจำไว้เสมอimplementsจะใช้เป็นพิเศษในการถ่ายทอดพฤติกรรมและextendsใช้ในการบอกคุณสมบัติ / ทรัพย์สิน

เราไม่ต้องการให้คุณสมบัติของเธรดแทนเราต้องการให้คลาสของเราทำงานเป็นงานที่สามารถรันได้


4

ใช่ถ้าคุณเรียกใช้การเรียก ThreadA ดังนั้นไม่จำเป็นต้องเรียกใช้วิธีการเริ่มต้นและวิธีการเรียกใช้เป็นการโทรหลังจากเรียกคลาส ThreadA เท่านั้น แต่ถ้าใช้การเรียกใช้ ThreadB คุณจำเป็นต้องใช้เธรดเริ่มต้นสำหรับวิธีการเรียกใช้ หากคุณมีความช่วยเหลือเพิ่มเติมตอบฉัน


4

ฉันพบว่ามีประโยชน์มากที่สุดในการใช้ Runnable ด้วยเหตุผลทั้งหมดที่กล่าวถึง แต่บางครั้งฉันชอบที่จะขยายเธรดเพื่อให้ฉันสามารถสร้างวิธีการหยุดเธรดของฉันเองและเรียกมันโดยตรงบนเธรดที่ฉันสร้างขึ้น


4

Java ไม่รองรับการสืบทอดหลายอย่างดังนั้นหากคุณขยายคลาส Thread ดังนั้นคลาสอื่นจะไม่ถูกขยาย

ตัวอย่างเช่น: หากคุณสร้างแอปเพล็ตมันจะต้องขยายคลาสแอปเพล็ตดังนั้นที่นี่วิธีเดียวที่จะสร้างเธรดได้คือการใช้ส่วนติดต่อแบบ Runnable


4

Runnableเป็นอินเทอร์เฟซในขณะที่Threadเป็นคลาสที่ใช้อินเทอร์เฟซนี้ จากมุมมองการออกแบบควรมีการแยกที่ชัดเจนระหว่างวิธีการกำหนดงานและระหว่างวิธีการทำงาน อดีตคือความรับผิดชอบของการRunnalbeดำเนินการและหลังเป็นงานของThreadชั้นเรียน ในกรณีส่วนใหญ่การใช้งานRunnableเป็นวิธีที่ถูกต้องในการติดตาม


4

ความแตกต่างระหว่างเธรดและ 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 ");
        }
    } 
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.