Bagaimana cara membuat utas Java menunggu keluaran utas lainnya?


128

Saya membuat aplikasi Java dengan aplikasi-logika-utas dan basis-akses-utas. Keduanya bertahan selama masa aplikasi dan keduanya harus berjalan pada saat yang sama (satu berbicara ke server, satu berbicara dengan pengguna; ketika aplikasi sepenuhnya dimulai, saya perlu keduanya untuk bekerja).

Namun, saat startup, saya perlu memastikan bahwa awalnya utas aplikasi menunggu hingga utas db siap (saat ini ditentukan dengan polling metode khusus dbthread.isReady()). Saya tidak keberatan jika aplikasi thread memblokir sampai thread db siap.

Thread.join() tidak terlihat seperti solusi - utas db hanya keluar saat aplikasi ditutup.

while (!dbthread.isReady()) {} jenis bekerja, tetapi loop kosong mengkonsumsi banyak siklus prosesor.

Ada ide lain? Terima kasih.

Jawaban:


128

Saya benar-benar akan merekomendasikan Anda melalui tutorial seperti Sun's Java Concurrency sebelum Anda memulai di dunia magis multithreading.

Ada juga sejumlah buku bagus (google untuk "Pemrograman Serentak di Jawa", "Java Concurrency in Practice").

Untuk mendapatkan jawaban Anda:

Dalam kode Anda yang harus menunggu dbThread, Anda harus memiliki sesuatu seperti ini:

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

Dalam dbThreadmetode Anda, Anda perlu melakukan sesuatu seperti ini:

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

The objectYouNeedToLockOnSaya menggunakan dalam contoh ini adalah sebaiknya obyek yang Anda butuhkan untuk memanipulasi secara bersamaan dari masing-masing thread, atau Anda bisa membuat terpisah Objectuntuk tujuan itu (saya tidak akan merekomendasikan membuat metode sendiri disinkronkan):

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

Untuk lebih jauh pemahaman Anda:
Ada cara lain (terkadang lebih baik) untuk melakukan hal di atas, misalnya dengan CountdownLatches, dll. Karena Java 5 ada banyak kelas concurrency yang bagus dalam java.util.concurrentpaket dan sub-paket. Anda benar-benar perlu menemukan materi online untuk mengetahui konkurensi, atau mendapatkan buku yang bagus.


Atau semua kode utas dapat diintegrasikan dengan baik dalam objek, jika saya tidak salah. Jadi saya tidak berpikir menggunakan sinkronisasi objek adalah cara yang baik untuk menerapkan pekerjaan terkait thread ini.
user1914692

@ user1914692: Tidak yakin apa jebakan yang ada dalam menggunakan pendekatan di atas - peduli untuk menjelaskan lebih lanjut?
Piskvor meninggalkan gedung

1
@Piskvor: Maaf saya sudah menulis ini sejak lama dan saya hampir lupa apa yang ada di pikiran saya. Mungkin saya hanya bermaksud lebih baik menggunakan kunci, daripada sinkronisasi objek, yang terakhir menjadi salah satu bentuk yang disederhanakan.
user1914692

Saya tidak mengerti bagaimana ini bekerja. Jika utas asedang menunggu objek, synchronised(object)bagaimana utas lain dapat synchronized(object)menelpon object.notifyAll()? Dalam program saya, semuanya macet di synchronozedblok.
Tomáš Zato - Reinstate Monica

@ TomášZato panggilan utas object.wait()secara efektif membuka kunci pada objek itu. Ketika utas kedua "keluar" dari blok yang disinkronkan, maka objek lain dilepaskan dari waitmetode dan mendapatkan kembali kunci pada titik itu.
rogerdpack

140

Gunakan CountDownLatch dengan penghitung 1.

CountDownLatch latch = new CountDownLatch(1);

Sekarang di utas aplikasi do-

latch.await();

Di utas db, setelah Anda selesai, lakukan -

latch.countDown();

4
Saya sangat suka solusi itu karena kesederhanaannya, meskipun mungkin lebih sulit untuk memahami makna kode pada pandangan pertama.
lethal-guitar

3
Penggunaan ini mengharuskan Anda untuk membuat kembali kait ketika mereka terbiasa. Untuk mendapatkan penggunaan yang mirip dengan Acara yang Dapat Ditunggu di Windows, Anda harus mencoba BooleanLatch, atau CountDownLatch yang dapat disetel ulang: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 / ...
phyatt

1
Hai, jika saya pertama kali memanggil metode async yang seharusnya memecat suatu peristiwa seperti: 1) asyncFunc (); 2) latch.await (); Kemudian saya hitung mundur dalam fungsi penanganan acara setelah diterima. Bagaimana saya bisa memastikan bahwa acara tidak akan ditangani SEBELUM latch.await () dipanggil? Saya ingin mencegah preemption antara jalur 1 dan 2. Terima kasih.
NioX5199

1
Untuk menghindari menunggu selamanya jika terjadi kesalahan, dimasukkan countDown()ke dalam finally{}blok
Daniel Alder

23

Persyaratan ::

  1. Untuk menunggu eksekusi utas berikutnya sampai selesai sebelumnya.
  2. Utas berikutnya tidak boleh mulai sampai utas sebelumnya berhenti, terlepas dari konsumsi waktu.
  3. Itu harus sederhana dan mudah digunakan.

Jawaban ::

@Lihat java.util.concurrent.Future.get () doc.

future.get () Menunggu jika perlu agar komputasinya selesai, dan kemudian mengambil hasilnya.

Pekerjaan selesai!! Lihat contoh di bawah ini

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

Output dari program ini ::

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

Anda dapat melihat bahwa membutuhkan 6sec sebelum menyelesaikan tugasnya yang lebih besar dari utas lainnya. Jadi Future.get () menunggu hingga tugas selesai.

Jika Anda tidak menggunakan future.get () itu tidak menunggu untuk menyelesaikan dan mengeksekusi konsumsi waktu berdasarkan.

Semoga Sukses dengan konkurensi Java.


Terima kasih atas jawaban Anda! Saya telah menggunakan CountdownLatches, tetapi Anda adalah pendekatan yang jauh lebih fleksibel.
Piskvor meninggalkan gedung

9

Banyak jawaban yang benar tetapi tanpa contoh sederhana .. Berikut adalah cara mudah dan sederhana cara menggunakannya 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();
        }
    }
}

Gunakan kelas ini seperti ini, lalu:

Buat ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

Dalam metode ini menunggu hasil:

resultsReady.await();

Dan dalam metode yang menciptakan hasil setelah semua hasil telah dibuat:

resultsReady.signal();

EDIT:

(Maaf untuk mengedit posting ini, tetapi kode ini memiliki kondisi lomba yang sangat buruk dan saya tidak memiliki reputasi yang cukup untuk berkomentar)

Anda hanya dapat menggunakan ini jika Anda yakin 100% bahwa sinyal () dipanggil setelah menunggu (). Ini adalah salah satu alasan utama mengapa Anda tidak dapat menggunakan objek Java seperti misalnya Windows Events.

Kode jika dijalankan dalam urutan ini:

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

maka utas 2 akan menunggu selamanya . Ini karena Object.notify () hanya membangunkan salah satu utas yang sedang berjalan. Utas yang menunggu nanti tidak dibangunkan. Ini sangat berbeda dari bagaimana saya berharap acara bekerja, di mana suatu peristiwa ditandai sampai a) menunggu atau b) secara eksplisit mengatur ulang.

Catatan: Sebagian besar waktu, Anda harus menggunakan notifyAll (), tetapi ini tidak relevan dengan masalah "tunggu selamanya" di atas.


7

Coba kelas CountDownLatch keluar dari java.util.concurrentpaket, yang menyediakan mekanisme sinkronisasi tingkat yang lebih tinggi, yang jauh lebih rentan kesalahan daripada hal-hal tingkat rendah.


6

Anda bisa melakukannya menggunakan Exchanger objek dibagi antara dua utas:

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

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

Dan di utas kedua:

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

}

Seperti yang telah dikatakan orang lain, jangan ambil kode copy-paste ini. Baca dulu.


4

The Future antarmuka darijava.lang.concurrent paket ini dirancang untuk memberikan akses ke hasil dihitung di thread lain.

Lihatlah FutureTask dan ExecutorService untuk cara yang sudah jadi dalam melakukan hal semacam ini.

Saya sangat merekomendasikan membaca Java Concurrency In Practice kepada siapa pun yang tertarik pada concurrency dan multithreading. Jelas berkonsentrasi pada Jawa, tetapi ada banyak daging bagi siapa pun yang bekerja dalam bahasa lain juga.


2

Jika Anda menginginkan sesuatu yang cepat dan kotor, Anda bisa menambahkan panggilan Thread.sleep () dalam loop sementara Anda. Jika pustaka database adalah sesuatu yang tidak dapat Anda ubah, maka sebenarnya tidak ada solusi mudah lainnya. Polling database sampai siap dengan masa tunggu tidak akan mematikan kinerja.

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

Bukan sesuatu yang bisa Anda sebut kode elegan, tetapi menyelesaikan pekerjaan.

Jika Anda dapat mengubah kode database, maka menggunakan mutex seperti yang diusulkan dalam jawaban lain lebih baik.


3
Ini cukup sibuk hanya menunggu. Menggunakan konstruksi dari paket util.concurrent Java 5 harus menjadi cara yang tepat. stackoverflow.com/questions/289434/... bagi saya adalah solusi terbaik untuk saat ini.
Cem Catikkas

Ini sibuk menunggu, tetapi jika hanya diperlukan di tempat khusus ini, dan jika tidak ada akses ke perpustakaan db, apa lagi yang bisa Anda lakukan?
Penantian yang

2

Ini berlaku untuk semua bahasa:

Anda ingin memiliki model acara / pendengar. Anda membuat pendengar untuk menunggu acara tertentu. Acara akan dibuat (atau diisyaratkan) di utas pekerja Anda. Ini akan memblokir utas sampai sinyal diterima alih-alih polling untuk melihat apakah suatu kondisi terpenuhi, seperti solusi yang Anda miliki saat ini.

Situasi Anda adalah salah satu penyebab kebuntuan yang paling umum - pastikan Anda memberi tanda utas lainnya terlepas dari kesalahan yang mungkin terjadi. Contoh-jika aplikasi Anda melempar pengecualian- dan tidak pernah memanggil metode untuk memberi sinyal yang lain bahwa semuanya telah selesai. Ini akan membuatnya jadi utas lainnya tidak pernah 'bangun'.

Saya menyarankan agar Anda melihat konsep menggunakan event dan event handler untuk lebih memahami paradigma ini sebelum menerapkan kasus Anda.

Atau Anda dapat menggunakan panggilan fungsi pemblokiran menggunakan mutex- yang akan menyebabkan utas menunggu sumber daya menjadi gratis. Untuk melakukan ini, Anda perlu sinkronisasi utas yang baik - seperti:

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

Anda bisa membaca dari antrian pemblokiran di satu utas dan menulisnya di utas lain.


1

Sejak

  1. join() telah dikesampingkan
  2. Anda sudah menggunakan CountDownLatch dan
  3. Future.get () sudah diusulkan oleh pakar lain,

Anda dapat mempertimbangkan alternatif lain:

  1. meminta semua dariExecutorService

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

    Jalankan tugas yang diberikan, kembalikan daftar Futures yang memegang status dan hasil mereka ketika semuanya selesai.

  2. ForkJoinPool atau newWorkStealingPool dari Executors(karena Jawa 8 rilis)

    Membuat kumpulan thread yang bekerja-mencuri menggunakan semua prosesor yang tersedia sebagai level paralelisme targetnya.


-1

masukkan deskripsi gambar di sini

Gagasan ini bisa berlaku? Jika Anda menggunakan CountdownLatches atau Semaphores berfungsi sempurna tetapi jika Anda mencari jawaban termudah untuk wawancara saya pikir ini bisa diterapkan.


1
Bagaimana tidak apa-apa untuk wawancara tetapi tidak untuk kode aktual?
Piskvor meninggalkan gedung

Karena dalam hal ini berjalan berurutan satu menunggu yang lain. Solusi terbaik bisa menggunakan Semaphores karena menggunakan CountdownLatches yang merupakan jawaban terbaik yang diberikan di sini Thread tidak pernah tidur yang berarti menggunakan siklus CPU.
Franco

Tetapi intinya bukan "menjalankannya secara berurutan." Saya akan mengedit pertanyaan untuk membuat ini lebih jelas: Utas GUI menunggu hingga database siap, dan kemudian keduanya berjalan secara simultan selama sisa eksekusi aplikasi: Utas GUI mengirimkan perintah ke utas DB dan membaca hasil. (Dan lagi: apa gunanya kode yang dapat digunakan dalam wawancara tetapi tidak dalam kode aktual? Sebagian besar pewawancara teknis yang saya temui memiliki latar belakang kode dan akan mengajukan pertanyaan yang sama; plus saya membutuhkan benda ini untuk aplikasi yang sebenarnya Saya sedang menulis pada saat itu, bukan untuk mengerjakan pekerjaan rumah)
Piskvor meninggalkan gedung pada

1
Begitu. Ini adalah masalah konsumen produsen menggunakan semaphores. Saya akan mencoba melakukan satu contoh
Franco

Saya telah membuat proyek github.com/francoj22/SemProducerConsumer/blob/master/src/com/… . Ini bekerja dengan baik.
Franco
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.