จะทำให้เธรด Java รอเอาต์พุตของเธรดอื่นได้อย่างไร


128

ฉันกำลังสร้างแอปพลิเคชัน Java ด้วยแอปพลิเคชันตรรกะเธรดและเธรดการเข้าถึงฐานข้อมูล ทั้งสองอย่างคงอยู่ตลอดอายุการใช้งานของแอปพลิเคชันและทั้งคู่ต้องทำงานพร้อมกัน (คนหนึ่งคุยกับเซิร์ฟเวอร์คนหนึ่งคุยกับผู้ใช้เมื่อแอปเริ่มทำงานโดยสมบูรณ์ฉันต้องให้ทั้งสองคนทำงาน)

อย่างไรก็ตามในการเริ่มต้นฉันต้องตรวจสอบให้แน่ใจว่าในตอนแรกเธรดของแอปจะรอจนกว่าเธรด db จะพร้อม (ขณะนี้กำหนดโดยการสำรวจวิธีการที่กำหนดเองdbthread.isReady()) ฉันจะไม่รังเกียจถ้าเธรดแอปบล็อกจนกว่าเธรดฐานข้อมูลจะพร้อม

Thread.join() ดูเหมือนวิธีแก้ปัญหา - เธรดฐานข้อมูลจะออกเมื่อปิดแอปเท่านั้น

while (!dbthread.isReady()) {} ชนิดของการทำงาน แต่การวนรอบว่างจะกินรอบตัวประมวลผลมาก

ความคิดอื่น ๆ ? ขอบคุณ

คำตอบ:


128

ฉันอยากจะแนะนำให้คุณอ่านบทช่วยสอนเช่นJava Concurrency ของ Sunก่อนที่คุณจะเริ่มต้นในโลกแห่งเวทมนตร์ของมัลติเธรด

นอกจากนี้ยังมีหนังสือดีๆอีกมากมาย (google สำหรับ "Concurrent Programming in Java", "Java Concurrency in Practice"

เพื่อรับคำตอบของคุณ:

ในรหัสของคุณที่ต้องรอdbThreadคุณต้องมีสิ่งนี้:

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

ในdbThreadวิธีการของคุณคุณจะต้องทำสิ่งนี้:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

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

private final Object lock = new Object();
//now use lock in your synchronized blocks

เพื่อให้คุณเข้าใจมากขึ้น:
มีวิธีอื่น ๆ (บางครั้งดีกว่า) ในการดำเนินการดังกล่าวข้างต้นเช่นด้วยCountdownLatchesเป็นต้นเนื่องจาก Java 5 มีคลาสพร้อมกันที่ดีจำนวนมากในjava.util.concurrentแพ็คเกจและแพ็กเกจย่อย คุณต้องหาสื่อออนไลน์เพื่อทำความรู้จักกับภาวะพร้อมกันหรือหาหนังสือดีๆสักเล่ม


รหัสเธรดทั้งหมดไม่สามารถรวมเข้ากับวัตถุได้อย่างดีถ้าฉันไม่ผิด ดังนั้นฉันไม่คิดว่าการใช้การซิงโครไนซ์อ็อบเจ็กต์เป็นวิธีที่ดีในการดำเนินงานที่เกี่ยวข้องกับเธรดนี้
user1914692

@ user1914692: ไม่แน่ใจว่ามีข้อผิดพลาดอะไรบ้างในการใช้แนวทางข้างต้น - ต้องการอธิบายเพิ่มเติมหรือไม่?
Piskvor ออกจากอาคาร

1
@Piskvor: ขอโทษที่ฉันเขียนเมื่อนานมาแล้วและฉันเกือบลืมสิ่งที่อยู่ในใจ บางทีฉันอาจจะตั้งใจที่จะใช้การล็อกมากกว่าการซิงโครไนซ์วัตถุรูปแบบหลังเป็นรูปแบบที่เรียบง่ายรูปแบบหนึ่งของรูปแบบเดิม
user1914692

ฉันไม่เข้าใจว่ามันทำงานอย่างไร ถ้าด้ายaกำลังรอบนวัตถุในsynchronised(object)วิธีหัวข้ออื่นสามารถไปถึงsynchronized(object)จะเรียกobject.notifyAll()? ในโปรแกรมของฉันทุกอย่างติดอยู่ในsynchronozedบล็อก
Tomáš Zato - คืนสถานะ Monica

@ TomášZatoเธรดแรกที่เรียกobject.wait()ใช้อย่างมีประสิทธิภาพในการปลดล็อกการล็อกวัตถุนั้น เมื่อเธรดที่สอง "ออกจาก" บล็อกที่ซิงโครไนซ์แล้วอ็อบเจ็กต์อื่น ๆ จะถูกปล่อยออกจากwaitเมธอดและรับการล็อกที่จุดนั้นอีกครั้ง
rogerdpack

140

ใช้CountDownLatch ที่มีตัวนับ 1

CountDownLatch latch = new CountDownLatch(1);

ตอนนี้อยู่ในเธรดแอป do-

latch.await();

ในเธรด db หลังจากเสร็จสิ้นให้ทำ -

latch.countDown();

4
ฉันชอบวิธีแก้ปัญหานี้มากเพราะมันเรียบง่ายแม้ว่ามันอาจจะยากกว่าที่จะเข้าใจความหมายของรหัสตั้งแต่แรกเห็น
กีตาร์สุด

3
การใช้งานนี้ต้องการให้คุณสร้างสลักใหม่เมื่อใช้งานหมดแล้ว ในการรับการใช้งานที่คล้ายกับ Waitable Event ใน Windows คุณควรลอง BooleanLatch หรือ CountDownLatch ที่รีเซ็ตได้: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 / …
phyatt

1
สวัสดีถ้าฉันเรียกเมธอด async เป็นครั้งแรกว่ามันควรจะเริ่มเหตุการณ์เช่นนี้: 1) asyncFunc (); 2) latch.await (); จากนั้นฉันจะนับถอยหลังในฟังก์ชันการจัดการเหตุการณ์เมื่อได้รับ ฉันจะแน่ใจได้อย่างไรว่าเหตุการณ์จะไม่ถูกจัดการก่อนที่จะเรียก latch.await () ผมขอป้องกันใบจองระหว่างบรรทัดที่ 1 และ 2 ขอบคุณครับ
NioX5199

1
เพื่อหลีกเลี่ยงการรอตลอดไปในกรณีที่เกิดข้อผิดพลาดให้ใส่countDown()ลงในfinally{}บล็อก
Daniel Alder

23

ความต้องการ ::

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

คำตอบ ::

@ ดู java.util.concurrent.Future.get () doc.

future.get () รอถ้าจำเป็นเพื่อให้การคำนวณเสร็จสมบูรณ์จากนั้นจึงดึงผลลัพธ์ออกมา

งานเสร็จ !! ดูตัวอย่างด้านล่าง

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

ผลลัพธ์ของโปรแกรมนี้ ::

One...
One!!
Two...
Two!!
Three...
Three!!

คุณจะเห็นว่าใช้เวลา 6 วินาทีก่อนที่จะเสร็จสิ้นภารกิจซึ่งมากกว่าเธรดอื่น ๆ ดังนั้น Future.get () รอจนกว่างานจะเสร็จ

หากคุณไม่ใช้ future.get () จะไม่รอให้เสร็จสิ้นและดำเนินการตามการใช้เวลา

ขอให้โชคดีกับ Java พร้อมกัน


ขอบคุณสำหรับคำตอบ! ฉันใช้CountdownLatches แล้ว แต่วิธีนี้เป็นวิธีที่ยืดหยุ่นกว่ามาก
Piskvor ออกจากอาคาร

9

คำตอบที่ถูกต้องมากมาย แต่ไม่มีตัวอย่างง่ายๆ .. นี่คือวิธีการใช้งานที่ง่ายและสะดวกCountDownLatch:

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2

8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

ใช้คลาสนี้ดังนี้:

สร้าง ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

ในวิธีนี้กำลังรอผลลัพธ์:

resultsReady.await();

และในวิธีการที่สร้างผลลัพธ์หลังจากสร้างผลลัพธ์ทั้งหมดแล้ว:

resultsReady.signal();

แก้ไข:

(ขออภัยในการแก้ไขโพสต์นี้ แต่โค้ดนี้มีสภาพการแข่งขันที่แย่มากและฉันไม่มีชื่อเสียงมากพอที่จะแสดงความคิดเห็น)

คุณสามารถใช้ได้ก็ต่อเมื่อคุณแน่ใจ 100% ว่าสัญญาณ () ถูกเรียกหลังจากรอ () นี่เป็นสาเหตุใหญ่ที่ทำให้คุณไม่สามารถใช้วัตถุ Java เช่น Windows Events ได้

ถ้ารหัสทำงานตามลำดับนี้:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

แล้วด้าย 2 จะรอตลอดไป เนื่องจาก Object.notify () ปลุกเพียงหนึ่งในเธรดที่กำลังทำงานอยู่ เธรดที่รอในภายหลังจะไม่ถูกปลุก สิ่งนี้แตกต่างอย่างมากกับวิธีที่ฉันคาดหวังให้เหตุการณ์ทำงานโดยที่เหตุการณ์จะถูกส่งสัญญาณจนกว่า a) รอหรือ b) รีเซ็ตอย่างชัดเจน

หมายเหตุ: โดยส่วนใหญ่คุณควรใช้ alertAll () แต่ไม่เกี่ยวข้องกับปัญหา "รอตลอดไป" ข้างต้น


7

ลองใช้คลาสCountDownLatchจากjava.util.concurrentแพ็กเกจซึ่งมีกลไกการซิงโครไนซ์ระดับที่สูงขึ้นซึ่งมีโอกาสเกิดข้อผิดพลาดน้อยกว่าสิ่งใด ๆ ในระดับต่ำ


6

คุณสามารถทำได้โดยใช้วัตถุExchanger ที่ใช้ร่วมกันระหว่างสองเธรด:

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

และในหัวข้อที่สอง:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

อย่างที่คนอื่น ๆ บอกอย่าใช้ความคิดเบา ๆ เพียงแค่คัดลอกและวางโค้ด อ่านหนังสือก่อน


4

อนาคตอินเตอร์เฟซจากjava.lang.concurrentแพคเกจได้รับการออกแบบเพื่อให้สามารถเข้าถึงผลการคำนวณในหัวข้ออื่น

ลองดูFutureTaskและExecutorServiceสำหรับวิธีการสำเร็จรูปในการทำสิ่งนี้

ฉันขอแนะนำอย่างยิ่งให้อ่านJava Concurrency In Practiceสำหรับทุกคนที่สนใจในการทำงานพร้อมกันและมัลติเธรด เห็นได้ชัดว่ามันมุ่งเน้นไปที่ Java แต่ก็มีเนื้อมากมายสำหรับทุกคนที่ทำงานในภาษาอื่นด้วย


2

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

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

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

ในกรณีที่คุณสามารถแก้ไขรหัสฐานข้อมูลได้การใช้ mutex ตามที่เสนอในคำตอบอื่นจะดีกว่า


3
มันค่อนข้างยุ่งรอ การใช้โครงสร้างจากแพ็คเกจ util.concurrent ของ Java 5 น่าจะเป็นวิธีที่จะไป stackoverflow.com/questions/289434/…มองว่าฉันเป็นทางออกที่ดีที่สุดสำหรับตอนนี้
Cem Catikkas

กำลังรอคอย แต่ถ้าจำเป็นเฉพาะในสถานที่นี้และหากไม่มีการเข้าถึงไลบรารี db คุณจะทำอะไรได้อีก? การรอที่วุ่นวายไม่จำเป็นต้องชั่วร้ายเสมอไป
Mario Ortegón

2

สิ่งนี้ใช้ได้กับทุกภาษา:

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

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

ฉันขอแนะนำให้คุณพิจารณาแนวคิดของการใช้เหตุการณ์และตัวจัดการเหตุการณ์เพื่อทำความเข้าใจกระบวนทัศน์นี้ให้ดีขึ้นก่อนที่จะนำกรณีของคุณไปใช้

หรือคุณสามารถใช้การเรียกใช้ฟังก์ชันการบล็อกโดยใช้ mutex ซึ่งจะทำให้เธรดต้องรอให้ทรัพยากรว่าง ในการทำสิ่งนี้คุณต้องมีการซิงโครไนซ์เธรดที่ดีเช่น:

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b

2

คุณสามารถอ่านจากคิวการบล็อกในเธรดหนึ่งและเขียนลงในอีกเธรดได้


1

ตั้งแต่

  1. join() ถูกตัดออก
  2. คุณได้ใช้CountDownLatchและ
  3. Future.get ()เสนอโดยผู้เชี่ยวชาญคนอื่น ๆ แล้ว

คุณสามารถพิจารณาทางเลือกอื่น ๆ :

  1. เรียกใช้ทั้งหมดจากExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

    ดำเนินงานที่กำหนดส่งคืนรายการ Futures ที่มีสถานะและผลลัพธ์เมื่อทั้งหมดเสร็จสมบูรณ์

  2. ForkJoinPoolหรือnewWorkStealingPoolจากExecutors(ตั้งแต่ Java 8 release)

    สร้างเธรดพูลที่ขโมยงานโดยใช้โปรเซสเซอร์ที่มีอยู่ทั้งหมดเป็นระดับความขนานของเป้าหมาย


-1

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

แนวคิดนี้สามารถนำไปใช้?. หากคุณใช้ CountdownLatches หรือ Semaphores ทำงานได้อย่างสมบูรณ์แบบ แต่ถ้าคุณกำลังมองหาคำตอบที่ง่ายที่สุดสำหรับการสัมภาษณ์ฉันคิดว่าสามารถใช้ได้


1
วิธีนี้เป็นอย่างไรสำหรับการสัมภาษณ์ แต่ไม่ใช่สำหรับรหัสจริง?
Piskvor ออกจากอาคาร

เนื่องจากในกรณีนี้จะทำงานตามลำดับหนึ่งรออีก ทางออกที่ดีที่สุดอาจใช้ Semaphores เนื่องจากการใช้ CountdownLatches ซึ่งเป็นคำตอบที่ดีที่สุดที่ได้รับที่นี่ Thread ไม่เข้าสู่โหมดสลีปซึ่งหมายถึงการใช้รอบ CPU
Franco

แต่ประเด็นไม่ใช่ "เรียกใช้ตามลำดับ" ฉันจะแก้ไขคำถามเพื่อให้ชัดเจนขึ้น: เธรด GUI รอจนกว่าฐานข้อมูลจะพร้อมจากนั้นทั้งสองจะทำงานพร้อมกันสำหรับการทำงานของแอปที่เหลือ: เธรด GUI ส่งคำสั่งไปยังเธรด DB และอ่านผลลัพธ์ (และอีกครั้ง: อะไรคือจุดของรหัสที่สามารถใช้ในการสัมภาษณ์ แต่ไม่ใช่ในโค้ดจริงผู้สัมภาษณ์ด้านเทคนิคส่วนใหญ่ที่ฉันเคยพบมีพื้นฐานด้านโค้ดและจะถามคำถามเดียวกันบวกกับฉันต้องการสิ่งนี้สำหรับแอปจริง ฉันกำลังเขียนในเวลานั้นไม่ใช่เพื่อการทำการบ้าน)
Piskvor ออกจากอาคาร

1
ดังนั้น. นี่คือปัญหาของผู้บริโภคผู้ผลิตโดยใช้เซมาโฟร์ ฉันจะพยายามทำตัวอย่างหนึ่ง
Franco

ผมได้สร้างโครงการgithub.com/francoj22/SemProducerConsumer/blob/master/src/com/... มันใช้งานได้ดี
Franco
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.