Diğer konuların bitip bitmediğini nasıl anlarım?


126

StartDownload()Üç iş parçacığını başlatan, adlı bir yönteme sahip bir nesnem var .

Her iş parçacığının yürütülmesi bittiğinde nasıl bildirim alırım?

İş parçacığından birinin (veya tümünün) bitip bitmediğini veya yürütülmekte olup olmadığını bilmenin bir yolu var mı?


Yanıtlar:


228

Bunu yapmanın birkaç yolu vardır:

  1. Her İş Parçacığının tamamlanması için engelleyici bir şekilde beklemek için ana iş parçacığınızda Thread.join () kullanın veya
  2. Her bir Thread tamamlanana kadar beklemek için Thread.isAlive () yöntemini yoklama şeklinde kontrol edin - genellikle önerilmez - veya
  3. Alışılmışın dışında, söz konusu her İş Parçacığı için, nesnenizde bir yöntemi çağırmak için setUncaughtExceptionHandler'ı çağırın ve tamamlandığında her İş Parçacığını yakalanmamış bir İstisna atacak şekilde programlayın veya
  4. Java.util.concurrent'daki kilitler veya eşzamanlayıcılar veya mekanizmalar kullanın veya
  5. Daha ortodoks, ana Konu'unuzda bir dinleyici oluşturun ve ardından her bir Konu'yu dinleyiciye tamamladıklarını söyleyecek şekilde programlayın.

Fikir # 5 nasıl uygulanır? Bunun bir yolu, önce bir arayüz oluşturmaktır:

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

ardından aşağıdaki sınıfı oluşturun:

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

ve daha sonra Konularınızın her biri genişleyecek NotifyingThreadve uygulamak yerine run()uygulayacaktır doRun(). Böylece tamamladıklarında, bildirim bekleyen herkesi otomatik olarak bilgilendireceklerdir.

Son olarak, ana sınıfınızda - tüm İş Parçacıkları'nı (veya en azından bildirim için bekleyen nesneyi) başlatan sınıfta - bu sınıfı implement ThreadCompleteListenerher İş Parçacığını oluşturana kadar değiştirin ve hemen sonra kendisini dinleyiciler listesine ekleyin:

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

daha sonra, her İş Parçacığı çıkarken, notifyOfThreadCompleteyönteminiz henüz tamamlanan (veya çöken) Thread örneği ile çağrılacaktır.

Bu daha iyi Not olacaktır implements Runnableziyade extends Threadiçin NotifyingThreadKonu uzanan genellikle yeni kodda önerilmez olarak. Ama sorunuzu kodluyorum. Eğer değiştirirseniz NotifyingThreaduygulamak için sınıf Runnableo zaman yapmak oldukça basittir konu yöneten kod, bazı değiştirmek zorunda.


Selam!! Son fikri beğendim. Bunu yapmak için bir dinleyici uygulamaya koymalı mıyım? Teşekkürler
Ricardo Felgueiras

4
ama bu yaklaşımı kullanarak, notifiyListeners run () içinde çağrılır, bu yüzden iş parçacığı için ısrar edecek ve diğer çağrılar da orada yapılacaktır, bu şekilde değil mi?
Jordi Puigdellívol

1
@Eddie Jordi, notifyyöntemi yöntem içinde değil de sonrasında çağırmanın mümkün olup olmadığını soruyordu run.
Tomasz Dzięcielewski

1
Sorusu gerçekten: nasıl elde edersiniz KAPALI şimdi ikincil vida. Bittiğini biliyorum ama şimdi Ana iş parçacığına nasıl erişebilirim ?
Patrick

1
Bu ileti dizisi güvenli mi? NotifyListeners'ın (ve dolayısıyla notifyOfThreadComplete), Dinleyiciyi yaratan iş parçacığı içinde değil, NotifyingThread içinde çağrılacağı görülmektedir.
Aaron

13

CyclicBarrier kullanarak çözüm

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 - döngüsel bariyer, yürütme iş parçacığı sayısına eşit partilerin sayısı artı ana yürütme iş parçacığı için bir tane ile oluşturulur (burada startDownload () yürütülüyor)

etiket 1 - n-inci İndiriliyorThread bekleme odasına girer

etiket 3 - NUMBER_OF_DOWNLOADING_THREADS bekleme odasına girdi. Ana yürütme dizisi, indirme işlerini aşağı yukarı aynı anda yapmaya başlamalarını sağlar.

etiket 4 - ana yürütme dizisi bekleme odasına girer. Bu, kodun anlaşılması en zor kısmıdır. Hangi ipliğin bekleme odasına ikinci kez gireceği önemli değildir. Odaya en son giren iş parçacığının, diğer tüm indirme iş parçacıklarının indirme işlerini bitirmesini sağlaması önemlidir.

etiket 2 - n-inci DownloadingThread indirme işini bitirdi ve bekleme odasına girdi. Sonuncuysa, yani ana yürütme iş parçacığı da dahil olmak üzere zaten NUMBER_OF_DOWNLOADING_THREADS girdiyse, ana iş parçacığı yalnızca diğer tüm evreler indirmeyi bitirdiğinde yürütülmeye devam edecektir.


9

Gerçekten kullanan bir çözümü tercih etmelisiniz java.util.concurrent. Konuyla ilgili Josh Bloch ve / veya Brian Goetz'i bulun ve okuyun.

java.util.concurrent.*Threads kullanmıyorsanız ve doğrudan kullanmak için sorumluluk alıyorsanız, muhtemelen join()bir iş parçacığının ne zaman yapıldığını bilmek için kullanmalısınız . İşte süper basit bir Geri Arama mekanizması. Önce geri aranacak şekilde Runnablearayüzü genişletin :

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

Ardından, çalıştırılabilirinizi çalıştıracak ve bittiğinde sizi geri arayacak bir Cellat yapın.

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

}

CallbackRunnableArayüzünüze eklenecek diğer bir tür bariz şey, herhangi bir istisnayı ele public void uncaughtException(Throwable e);almanın bir yoludur , bu nedenle oraya bir satır koyun ve uygulayıcınıza sizi bu arayüz yöntemine göndermek için bir Thread.UncaughtExceptionHandler kurun.

Ama tüm bunları yapmak gerçekten kokmaya başlar java.util.concurrent.Callable. java.util.concurrentProjeniz izin veriyorsa gerçekten kullanmaya bakmalısınız .


İş runner.join()parçacığının bittiğini bildiğiniz için , bu geri arama mekanizmasından ne kazandığınız konusunda basitçe aramak ve daha sonra istediğiniz kod hakkında biraz net değilim . Sadece bu kodu çalıştırılabilirin bir özelliği olarak tanımlamanız mı, böylece farklı çalıştırılabilirler için farklı şeylere sahip olabilirsiniz?
Stephen

2
Evet, runner.join()beklemenin en doğrudan yolu. OP'nin her indirme için "bilgilendirilmek" istediği için ana arama iş parçacığını engellemek istemediğini varsaydım, bu herhangi bir sırada tamamlanabilirdi. Bu, eşzamansız olarak bildirim almanın bir yolunu sunuyordu.
broc.seib

4

Bitmelerini beklemek ister misin? Eğer öyleyse, Join yöntemini kullanın.

Sadece kontrol etmek isterseniz isAlive özelliği de vardır.


3
İş parçacığı henüz yürütülmeye başlamadıysa isAlive'ın false döndürdüğünü unutmayın (kendi iş parçacığınız zaten üzerinde start çağırmış olsa bile).
Tom Hawtin - tackline

@ TomHawtin-tackline bundan oldukça emin misiniz? Java dokümantasyonuyla çelişir ("Bir iş parçacığı başlatılmışsa ve henüz ölmemişse canlıdır " - docs.oracle.com/javase/6/docs/api/java/lang/… ). Buradaki cevaplarla da çelişirdi ( stackoverflow.com/questions/17293304/… )
Stephen

@Stephen Bunu yazmayalı uzun zaman oldu, ama doğru gibi görünüyor. Bunun dokuz yıl önce hafızamda taze olan diğer insanların sorunlarına neden olduğunu hayal ediyorum. Tam olarak neyin gözlemlenebilir olduğu uygulamaya bağlı olacaktır. Bir haber Threadiçin startiplik işlevi gören, ancak arama döner hemen. isAlivebasit bir bayrak testi olmalı, ancak Google'da araştırdığımda yöntem native.
Tom Hawtin - tackline

4

Aşağıdaki değerlerden biriyle Thread.State numaralandırmasının bir örneğini döndüren getState () ile iş parçacığı örneğini sorgulayabilirsiniz:

*  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.

Ancak, 3 çocuğun bitirmesini bekleyen bir ana iş parçacığına sahip olmanın daha iyi bir tasarım olacağını düşünüyorum, diğer 3'ü bitirdiğinde usta daha sonra yürütmeye devam edecektir.


3 çocuğun çıkmasını beklemek kullanım paradigmasına uymayabilir. Bu bir indirme yöneticisiyse, 15 indirmeye başlamak ve durum çubuğundan durumu kaldırmak veya bir indirme tamamlandığında kullanıcıyı uyarmak isteyebilir, bu durumda geri arama daha iyi çalışacaktır.
digitaljoel

3

ExecutorService iş parçacığı havuzu Executorsoluşturmak için nesneyi de kullanabilirsiniz . Ardından, her bir iş parçacığınızı çalıştırmak ve Vadeli İşlemleri almak için yöntemi kullanın. Bu, tümü yürütmeyi bitirene kadar engellenecektir. Diğer seçeneğiniz, her birini havuzu kullanarak yürütmek ve ardından havuzun yürütülmesi bitene kadar bloke etmek için çağrı yapmak olacaktır. Görev eklemeyi bitirdiğinizde () çağırdığınızdan emin olun .invokeAllawaitTerminationshutdown


2

Son 6 yılda çok iş parçacıklı cephede birçok şey değişti.

join()API kullanmak ve kilitlemek yerine kullanabilirsiniz.

1. ExecutorService invokeAll() API

Verilen görevleri yürütür, tümü tamamlandığında statülerini ve sonuçlarını tutan bir Vadeli İşlemler listesi döndürür.

2. CountDownLatch

Bir veya daha fazla iş parçacığının diğer iş parçacıklarında gerçekleştirilen bir dizi işlem tamamlanana kadar beklemesine izin veren bir eşitleme yardımı.

A CountDownLatch, belirli bir sayı ile başlatılır. Await yöntemleri, countDown()yöntemin çağrıları nedeniyle geçerli sayı sıfıra ulaşana kadar bloklanır , bundan sonra tüm bekleyen iş parçacıkları serbest bırakılır ve sonraki tüm await dönüş çağrıları hemen döner. Bu tek seferlik bir olaydır - sayı sıfırlanamaz. Sayımı sıfırlayan bir sürüme ihtiyacınız varsa, CyclicBarrier kullanmayı düşünün.

3. ForkJoinPool veya newWorkStealingPool()içinde yürütücüleri için diğer bir yoldur

Bütün aracılığıyla 4.Iterate Futureüzerinde teslim gelen görevler ExecutorServiceve çağrı engelleme ile durumlarını kontrol get()üzerinde Futurenesne

İlgili SE sorularına bir göz atın:

Kendi iş parçacığını ortaya çıkaran bir iş parçacığı nasıl beklenir?

Yürütücüler: Görevler yinelemeli olarak oluşturulursa, tüm görevler bitene kadar eşzamanlı olarak nasıl beklenir?


2

Thread sınıfı için javadoc'a bakmanızı öneririm .

İplik manipülasyonu için birden fazla mekanizmanız var.

  • Ana iş parçacığınız join()üç iş parçacığını seri olarak yapabilir ve üçü de tamamlanana kadar devam edemez.

  • Aralıklarla ortaya çıkan dişlerin iş parçacığı durumunu yoklayın.

  • Ayrı içine kökenli parçacığı tüm koyun ThreadGroupve anket activeCount()üzerinde ThreadGroupve 0'a almak için bekleyin.

  • İş parçacıkları arası iletişim için özel bir geri arama veya dinleyici tipi arabirim ayarlayın.

Eminim hala kaçırdığım birçok yol vardır.


1

İşte basit, kısa, anlaşılması kolay ve benim için mükemmel çalışan bir çözüm. Başka bir konu bittiğinde ekrana çizim yapmam gerekiyordu; ama yapamadı çünkü ana iş parçacığı ekranı kontrol ediyor. Yani:

(1) Global değişkeni ben yarattım: boolean end1 = false;İş parçacığı, biterken onu true olarak ayarlıyor. Bu, yanıtlandığı "postDelayed" döngüsü tarafından ana iş parçacığında toplanır.

(2) İleti dizim şunları içerir:

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) Neyse ki, "postDelayed" ana iş parçacığında çalışır, bu nedenle diğer iş parçacığı saniyede bir kontrol edilir. Diğer iş parçacığı bittiğinde, bu daha sonra yapmak istediğimiz her şeye başlayabilir.

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) Son olarak, aşağıdaki numarayı arayarak kodunuzun herhangi bir yerinde çalışmaya başlayın:

void startThread()
{
   myThread();
   checkThread();
}

1

Sanırım en kolay yol ThreadPoolExecutorsınıf kullanmak .

  1. Bir kuyruğu vardır ve kaç iş parçacığının paralel olarak çalışacağını ayarlayabilirsiniz.
  2. Güzel geri arama yöntemlerine sahiptir:

Kanca yöntemleri

Bu sınıf, her görevin yürütülmesinden önce ve sonra çağrılan korumalı geçersiz kılınabilen beforeExecute(java.lang.Thread, java.lang.Runnable)ve afterExecute(java.lang.Runnable, java.lang.Throwable)yöntemler sağlar . Bunlar, yürütme ortamını değiştirmek için kullanılabilir; örneğin, ThreadLocals'ı yeniden başlatmak, istatistikleri toplamak veya günlük girişleri eklemek. Ayrıca, terminated()Yürütücü tamamen feshedildikten sonra yapılması gereken herhangi bir özel işlemi gerçekleştirmek için yöntem geçersiz kılınabilir.

tam da ihtiyacımız olan şey bu. Biz geçersiz kılar afterExecute()her iş parçacığı bitti ve geçersiz kılar sonra geri aramalar almak için terminated()bütün ipler bittiğinde bilmek.

İşte yapmanız gerekenler

  1. Bir uygulayıcı oluşturun:

    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. Ve konularınızı başlatın:

    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 
    }

İç yöntem informUiThatWeAreDone();, tüm iş parçacıkları tamamlandığında yapmanız gereken her şeyi yapın, örneğin, kullanıcı arabirimini güncelleyin.

NOT:synchronized İşinizi paralel olarak yaptığınız için yöntemleri kullanmayı unutmayın ve synchronizedyöntemi başka bir synchronizedyöntemden çağırmaya karar verirseniz ÇOK DİKKATLİ OLUN ! Bu genellikle kilitlenmelere yol açar

Bu yardımcı olur umarım!



0

Thread sınıfı için Java belgelerine bakın. İş parçacığının durumunu kontrol edebilirsiniz. Üç evreyi üye değişkenlere koyarsanız, üç iş parçacığı da birbirlerinin durumlarını okuyabilir.

Yine de biraz dikkatli olmalısınız çünkü ipler arasında yarış koşullarına neden olabilirsiniz. Diğer iş parçacıklarının durumuna bağlı olarak karmaşık mantıktan kaçınmaya çalışın. Aynı değişkenlere birden çok iş parçacığı yazmaktan kesinlikle kaçının.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.