Bagaimana cara mengetahui apakah utas lainnya telah selesai?

126

Saya memiliki objek dengan metode bernama StartDownload(), yang dimulai tiga utas.

Bagaimana saya mendapat notifikasi ketika setiap utas telah selesai dieksekusi?

Apakah ada cara untuk mengetahui apakah satu (atau semua) utas sudah selesai atau masih dieksekusi?

Ricardo Felgueiras
sumber
1
Lihatlah kelas Java 5 Barrier
Fortyrunner

Jawaban:

228

Ada beberapa cara Anda dapat melakukan ini:

  1. Gunakan Thread.join () di utas utama Anda untuk menunggu dengan cara memblokir untuk setiap Thread untuk menyelesaikan, atau
  2. Periksa Thread.isAlive () dengan cara polling - umumnya tidak disarankan - untuk menunggu sampai setiap Thread selesai, atau
  3. Unorthodox, untuk setiap Thread yang dipermasalahkan, panggil setUncaughtExceptionHandler untuk memanggil metode di objek Anda, dan program setiap Thread untuk melemparkan Pengecualian yang tidak tertangkap saat selesai, atau
  4. Gunakan kunci atau sinkronisasi atau mekanisme dari java.util.concurrent , atau
  5. Lebih ortodoks, buat pendengar di utas utama Anda, dan kemudian program masing-masing utas Anda untuk memberi tahu pendengar bahwa mereka telah selesai.

Bagaimana cara menerapkan Ide # 5? Nah, salah satu caranya adalah pertama-tama membuat antarmuka:

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

lalu buat kelas berikut:

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

dan kemudian masing-masing Thread Anda akan diperluas NotifyingThreaddan alih-alih mengimplementasikannya run()akan menerapkan doRun(). Jadi ketika mereka selesai, mereka akan secara otomatis memberi tahu siapa pun yang menunggu pemberitahuan.

Terakhir, di kelas utama Anda - kelas yang memulai semua Thread (atau setidaknya objek yang menunggu pemberitahuan) - modifikasi kelas itu implement ThreadCompleteListenerdan segera setelah membuat setiap Thread, tambahkan sendiri ke daftar pendengar:

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

kemudian, saat setiap Thread keluar, notifyOfThreadCompletemetode Anda akan dipanggil dengan instance Thread yang baru saja selesai (atau macet).

Perhatikan bahwa lebih baik implements Runnabledaripada daripada extends Threadkarena NotifyingThreadmemperluas Thread biasanya tidak disarankan dalam kode baru. Tapi saya mengkode pertanyaan Anda. Jika Anda mengubah NotifyingThreadkelas untuk diimplementasikan Runnablemaka Anda harus mengubah beberapa kode Anda yang mengelola Thread, yang cukup mudah dilakukan.

Eddie
sumber
Hai!! Saya suka ide terakhir. Saya menerapkan pendengar untuk melakukan itu? Terima kasih
Ricardo Felgueiras
4
tetapi menggunakan pendekatan ini, notifiyListeners dipanggil di dalam run () sehingga akan disebut insisde utas dan panggilan selanjutnya akan dilakukan di sana juga, bukankah dengan cara ini?
Jordi Puigdellívol
1
@ Eddie Jordi bertanya, apakah mungkin untuk memanggil notifymetode tidak memasukkan runmetode, tetapi setelah itu.
Tomasz Dzięcielewski
1
Pertanyaan sebenarnya adalah: bagaimana Anda mendapatkan OFF dari utas sekunder sekarang. Saya tahu bahwa itu sudah selesai, tetapi bagaimana cara mengakses utas utama sekarang?
Patrick
1
Apakah utas ini aman? Tampaknya notifyListeners (dan karenanya notifyOfThreadComplete) akan dipanggil di dalam NotifyingThread, daripada di utas yang dibuat, buat Pendengar itu sendiri.
Aaron
13

Solusi menggunakan CyclicBarrier

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

label0 - penghalang siklik dibuat dengan jumlah pihak sama dengan jumlah utas pelaksana plus satu untuk utas utama pelaksanaan (di mana startDownload () dijalankan)

label 1 - n Download thThread memasuki ruang tunggu

label 3 - NUMBER_OF_DOWNLOADING_THREADS telah memasuki ruang tunggu. Utas utama eksekusi membebaskan mereka untuk mulai melakukan pekerjaan pengunduhan dalam waktu yang kurang lebih sama

label 4 - utas eksekusi masuk ke ruang tunggu. Ini adalah bagian 'paling sulit' dari kode yang harus dipahami. Tidak masalah utas mana yang akan memasuki ruang tunggu untuk kedua kalinya. Penting bahwa utas apa pun yang memasuki ruangan terakhir memastikan bahwa semua utas unduhan lainnya telah menyelesaikan pekerjaan pengunduhan mereka.

label 2 - Download nThth telah menyelesaikan pekerjaan pengunduhan dan memasuki ruang tunggu. Jika ini yang terakhir yaitu NUMBER_OF_DOWNLOADING_THREADS telah memasukkannya, termasuk utas utama eksekusi, utas utama akan melanjutkan eksekusi hanya ketika semua utas lainnya telah selesai mengunduh.

Boris Pavlovic
sumber
9

Anda harus benar - benar memilih solusi yang digunakan java.util.concurrent. Temukan dan baca Josh Bloch dan / atau Brian Goetz pada topik.

Jika Anda tidak menggunakan java.util.concurrent.*dan bertanggung jawab untuk menggunakan Thread secara langsung, maka Anda mungkin harus menggunakannya join()untuk mengetahui kapan sebuah thread selesai. Berikut adalah mekanisme Callback super sederhana. Pertama memperluas Runnableantarmuka untuk memiliki panggilan balik:

public interface CallbackRunnable extends Runnable {
    public void callback();
}

Kemudian buat seorang Pelaksana yang akan menjalankan runnable Anda dan memanggil Anda kembali setelah selesai.

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

Jenis hal lain yang jelas untuk ditambahkan ke CallbackRunnableantarmuka Anda adalah cara untuk menangani pengecualian, jadi mungkin letakkan public void uncaughtException(Throwable e);garis di sana dan di pelaksana Anda, instal Thread.UncaughtExceptionHandler untuk mengirim Anda ke metode antarmuka itu.

Tetapi melakukan semua yang benar-benar mulai berbau java.util.concurrent.Callable. Anda harus benar-benar melihat menggunakan java.util.concurrentjika proyek Anda mengizinkannya.

broc.seib
sumber
Saya sedikit tidak jelas tentang apa yang Anda dapatkan dari mekanisme panggilan balik ini vs hanya menelepon runner.join()dan kemudian kode apa pun yang Anda inginkan setelah itu, karena Anda tahu utasnya telah selesai. Apakah Anda hanya perlu mendefinisikan kode itu sebagai properti dari runnable, sehingga Anda dapat memiliki hal yang berbeda untuk runnable yang berbeda?
Stephen
2
Ya, runner.join()adalah cara paling langsung untuk menunggu. Saya berasumsi OP tidak ingin memblokir utas panggilan utama mereka karena mereka meminta untuk "diberitahu" untuk setiap unduhan, yang dapat diselesaikan dalam urutan apa pun. Ini menawarkan satu cara untuk mendapatkan pemberitahuan secara tidak sinkron.
broc.seib
4

Apakah Anda ingin menunggu sampai selesai? Jika demikian, gunakan metode Bergabung.

Ada juga properti isAlive jika Anda hanya ingin memeriksanya.

Jonathan Allen
sumber
3
Perhatikan bahwa isAlive mengembalikan false jika utas belum mulai dieksekusi (bahkan jika utas Anda sendiri telah memanggil start on it).
Tom Hawtin - tackline
@ TomHawtin-tackline apakah Anda cukup yakin tentang itu? Ini akan bertentangan dengan dokumentasi Java ("Sebuah utas masih hidup jika sudah dimulai dan belum mati" - docs.oracle.com/javase/6/docs/api/java/lang/… ). Itu juga akan bertentangan dengan jawaban di sini ( stackoverflow.com/questions/17293304/... )
Stephen
@Stephen Sudah lama sejak saya menulis itu, tapi sepertinya itu benar. Saya membayangkan itu menyebabkan masalah orang lain yang segar dalam ingatan saya sembilan tahun yang lalu. Apa yang bisa diamati akan tergantung pada implementasi. Anda memberitahu Threaduntuk start, yang benang tidak, tapi panggilan kembali segera. isAliveharus menjadi tes bendera sederhana, tetapi ketika saya googled metode itu native.
Tom Hawtin - tackline
4

Anda bisa menginterogasi instance thread dengan getState () yang mengembalikan instance enumerasi Thread.State dengan salah satu nilai berikut:

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

Namun saya pikir itu akan menjadi desain yang lebih baik untuk memiliki thread utama yang menunggu 3 anak untuk menyelesaikan, master kemudian akan melanjutkan eksekusi ketika 3 lainnya selesai.

Miquel
sumber
Menunggu 3 anak untuk keluar mungkin tidak cocok dengan paradigma penggunaan. Jika ini adalah pengelola unduhan, mereka mungkin ingin memulai 15 unduhan dan cukup menghapus status dari bilah status atau memperingatkan pengguna ketika unduhan selesai, dalam hal ini panggilan balik akan berfungsi lebih baik.
digitaljoel
3

Anda juga bisa menggunakan Executorsobjek untuk membuat kumpulan utas ExecutorService . Kemudian gunakan invokeAllmetode untuk menjalankan masing-masing utas Anda dan mengambil Futures. Ini akan memblokir sampai semua eksekusi selesai. Pilihan Anda yang lain adalah mengeksekusi masing-masing menggunakan pool dan kemudian memanggil awaitTerminationuntuk memblokir sampai pool selesai dieksekusi. Pastikan untuk memanggil shutdown() saat Anda selesai menambahkan tugas.

J Gitter
sumber
2

Banyak hal telah diubah dalam 6 tahun terakhir di bagian multi-threading.

Alih-alih menggunakan join()dan mengunci API, Anda dapat menggunakan

1. API ExecutorService invokeAll()

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

2. CountDownLatch

Alat bantu sinkronisasi yang memungkinkan satu atau lebih utas untuk menunggu sampai satu set operasi dilakukan di utas lainnya selesai.

A CountDownLatchdiinisialisasi dengan jumlah yang diberikan. Metode menunggu menunggu hingga hitungan saat ini mencapai nol karena doa countDown()metode, setelah semua utas menunggu dirilis dan setiap doa selanjutnya menunggu segera kembali. Ini adalah fenomena sekali pakai - penghitungan tidak dapat diatur ulang. Jika Anda membutuhkan versi yang menyetel ulang hitungan, pertimbangkan untuk menggunakan CyclicBarrier.

3. ForkJoinPool atau newWorkStealingPool()di Pelaksana adalah cara lain

4.Iterate melalui semua Futuretugas dari menyerahkan ExecutorServicedan memeriksa status dengan memblokir panggilan get()pada Futureobjek

Lihat pertanyaan SE terkait:

Bagaimana cara menunggu utas yang memunculkan utas itu sendiri?

Pelaksana: Bagaimana cara menunggu secara sinkron sampai semua tugas selesai jika tugas dibuat secara rekursif?

Ravindra babu
sumber
2

Saya sarankan melihat javadoc untuk kelas Thread .

Anda memiliki banyak mekanisme untuk manipulasi utas.

  • Utas utama Anda dapat join()tiga utas secara berurutan, dan kemudian tidak akan dilanjutkan sampai ketiganya selesai.

  • Polling status utas dari spawned utas pada interval waktu tertentu.

  • Letakkan semua utas yang muncul ke tempat terpisah ThreadGroupdan polling activeCount()pada ThreadGroupdan tunggu sampai mencapai 0.

  • Menyiapkan jenis panggilan balik kustom atau jenis antarmuka pendengar untuk komunikasi antar utas.

Saya yakin ada banyak cara lain yang masih saya lewatkan.

digitaljoel
sumber
1

Inilah solusi yang sederhana, pendek, mudah dimengerti, dan bekerja dengan sempurna untuk saya. Saya perlu menggambar ke layar ketika utas lain berakhir; tetapi tidak bisa karena utas utama memiliki kendali atas layar. Begitu:

(1) Saya membuat variabel global: boolean end1 = false;Utas menyetelnya menjadi true saat mengakhiri. Itu diambil di mainthread oleh loop "postDelayed", di mana ia ditanggapi.

(2) Utas saya berisi:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) Untungnya, "postDelayed" berjalan di utas utama, jadi di situlah di periksa utas lainnya sekali setiap detik. Ketika utas lainnya berakhir, ini dapat memulai apa pun yang ingin kita lakukan selanjutnya.

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4) Akhirnya, mulai semuanya berjalan di suatu tempat dalam kode Anda dengan memanggil:

void startThread()
{
   myThread();
   checkThread();
}
DreamMaster Pro
sumber
1

Saya kira cara termudah adalah menggunakan ThreadPoolExecutorkelas.

  1. Ini memiliki antrian dan Anda dapat mengatur berapa banyak utas yang seharusnya bekerja secara paralel.
  2. Ini memiliki metode panggilan balik yang bagus:

Metode kait

Kelas ini menyediakan metode yang tidak dapat ditimpa beforeExecute(java.lang.Thread, java.lang.Runnable)dan afterExecute(java.lang.Runnable, java.lang.Throwable)metode yang dipanggil sebelum dan sesudah eksekusi setiap tugas. Ini dapat digunakan untuk memanipulasi lingkungan eksekusi; misalnya, menginisialisasi ulang ThreadLocals, mengumpulkan statistik, atau menambahkan entri log. Selain itu, metode terminated()dapat diganti untuk melakukan pemrosesan khusus yang perlu dilakukan setelah Pelaksana telah sepenuhnya dihentikan.

itulah yang kita butuhkan. Kami akan mengganti afterExecute()untuk mendapatkan panggilan balik setelah setiap utas selesai dan akan menimpa terminated()untuk mengetahui kapan semua utas selesai.

Jadi inilah yang harus Anda lakukan

  1. Buat pelaksana:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. Dan mulai utas Anda:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

Metode di dalam informUiThatWeAreDone();melakukan apa pun yang perlu Anda lakukan ketika semua utas selesai, misalnya, perbarui UI.

CATATAN: Jangan lupa tentang menggunakan synchronizedmetode karena Anda melakukan pekerjaan Anda secara paralel dan JADILAH SANGAT BERHATI-HATI jika Anda memutuskan untuk memanggil synchronizedmetode dari synchronizedmetode lain ! Ini sering menyebabkan kebuntuan

Semoga ini membantu!

Kirill Karmazin
sumber
0

Anda juga bisa menggunakan SwingWorker, yang memiliki dukungan perubahan properti bawaan. Lihat addPropertyChangeListener () atau metode get () untuk contoh perubahan pendengar keadaan.

akarnokd
sumber
0

Lihatlah dokumentasi Java untuk kelas Thread. Anda dapat memeriksa status utas. Jika Anda memasukkan ketiga utas ke dalam variabel anggota, maka ketiga utas tersebut dapat saling membaca status masing-masing.

Anda harus sedikit berhati-hati, karena Anda dapat menyebabkan kondisi balapan di antara utas. Coba saja hindari logika yang rumit berdasarkan keadaan utas lainnya. Jelas menghindari beberapa utas menulis ke variabel yang sama.

Don Kirkby
sumber