Bir Java iş parçacığının başka bir iş parçacığının çıktısını beklemesi nasıl sağlanır?


128

Uygulama mantığı ve veritabanı erişim iş parçacığı içeren bir Java uygulaması yapıyorum. Her ikisi de uygulamanın ömrü boyunca devam ediyor ve her ikisinin de aynı anda çalışıyor olması gerekiyor (biri sunucuyla konuşuyor, biri kullanıcıyla konuşuyor; uygulama tamamen başlatıldığında, ikisinin de çalışmasına ihtiyacım var ).

Bununla birlikte, başlangıçta, başlangıçta uygulama iş parçacığının db iş parçacığı hazır olana kadar beklediğinden emin olmalıyım (şu anda özel bir yöntem yoklayarak belirlenir dbthread.isReady()). Uygulama iş parçacığı, db iş parçacığı hazır olana kadar engellenirse sorun olmaz.

Thread.join() bir çözüm gibi görünmüyor - db iş parçacığı yalnızca uygulama kapatıldığında çıkar.

while (!dbthread.isReady()) {} bir tür işe yarıyor, ancak boş döngü çok fazla işlemci döngüsü tüketiyor.

Başka fikrin var mı? Teşekkürler.

Yanıtlar:


128

Çoklu kullanımın büyülü dünyasına başlamadan önce Sun's Java Concurrency gibi bir öğreticiden geçmenizi gerçekten tavsiye ederim .

Ayrıca bir dizi iyi kitap da bulunmaktadır ("Java'da Eşzamanlı Programlama", "Uygulamada Java Eşzamanlılığı" için google.

Cevabınıza ulaşmak için:

Kodunuzda beklemesi dbThreadgereken, şöyle bir şeye sahip olmalısınız:

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

Senin içinde dbThreadbireyin yöntemle, böyle bir şey yapmak gerekir:

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

objectYouNeedToLockOnBen bu örneklerde kullanıyorum her iplikten eşzamanlı işlemek gerektiğini tercihen nesne, ya da sen ayrı oluşturabilir Object(ı kendileri senkronize verme yöntemleri tavsiye etmem) bu amaçla:

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

Anlayışınızı ilerletmek için:
Yukarıdakileri yapmanın başka (bazen daha iyi) yolları vardır, örneğin ile CountdownLatches, vb. Java 5'ten beri java.util.concurrentpakette ve alt paketlerde pek çok şık eşzamanlılık sınıfı vardır . Eşzamanlılığı tanımak veya iyi bir kitap almak için gerçekten çevrimiçi malzeme bulmanız gerekiyor.


Ya da yanılmıyorsam tüm iş parçacığı kodu nesnelere iyi bir şekilde entegre edilemez. Bu yüzden nesne senkronizasyonunu kullanmanın bu iş parçacığı ile ilgili çalışmayı gerçekleştirmenin iyi bir yolu olduğunu düşünmüyorum.
user1914692

@ user1914692: Yukarıdaki yaklaşımı kullanırken ne gibi tuzaklar olduğundan emin değilsiniz - daha fazla açıklamaya dikkat edin?
Piskvor

1
@Piskvor: Özür dilerim bunu uzun zaman önce yazdım ve neredeyse aklımdan geçenleri unutuyordum. Belki sadece nesne senkronizasyonundan ziyade kilidi kullanmayı kastetmiştim, ikincisi birincisinin basitleştirilmiş bir şeklidir.
user1914692

Bunun nasıl çalıştığını anlamıyorum. İplik Eğer anesne bekliyor synchronised(object)nasıl başka bir iş parçacığı gidebilirsiniz synchronized(object)çağırmak için object.notifyAll()? Programımda her şey synchronozedbloklara takıldı .
Tomáš Zato - Monica'yı eski durumuna getir

@ TomášZato ilk iş parçacığı object.wait(), o nesnedeki kilidin etkin bir şekilde kilidini açmayı çağırır . İkinci iş parçacığı, senkronize edilmiş bloğundan "çıktığı" zaman, diğer nesneler waityöntemden serbest bırakılır ve bu noktada kilidi yeniden elde eder.
rogerdpack

140

1 sayacı olan bir CountDownLatch kullanın .

CountDownLatch latch = new CountDownLatch(1);

Şimdi uygulama başlığında do-

latch.await();

Db iş parçacığında, işiniz bittikten sonra şunu yapın -

latch.countDown();

4
İlk bakışta kodun anlamını kavramak daha zor olsa da, bu çözümü basit olduğu için gerçekten seviyorum.
lethal-guitar

3
Bu kullanım, kullandıklarında mandalı yeniden yapmanızı gerektirir. Windows'ta Beklenebilir Olaya benzer kullanım elde etmek için, bir BooleanLatch veya sıfırlanabilir bir CountDownLatch denemelisiniz : docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
phyatt

1
Merhaba, ilk olarak bir olayı tetiklemesi gereken bir zaman uyumsuz yöntemi çağırırsam: 1) asyncFunc (); 2) latch.await (); Sonra olay işleme fonksiyonunda alındıktan sonra geri sayıyorum. Latch.await () çağrılmadan ÖNCE olayın ele alınmayacağından nasıl emin olabilirim? 1. ve 2. satırlar arasında ön ödeme yapılmasını önlemek istiyorum. Teşekkürler.
NioX5199

1
Hata durumunda sonsuza kadar beklemekten kaçınmak için, countDown()bir finally{}bloğa koyun
Daniel Alder

23

Gereksinim ::

  1. Bir önceki işlem bitene kadar sonraki iş parçacığının yürütülmesini beklemek
  2. Zaman tüketimine bakılmaksızın, sonraki iş parçacığı önceki iş parçacığı durana kadar başlamamalıdır.
  3. Basit ve kullanımı kolay olmalıdır.

Cevap ::

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

Future.get () Gerekirse hesaplamanın tamamlanmasını bekler ve ardından sonucunu alır.

İş bitmiş!! Aşağıdaki örneğe bakın

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

Bu programın çıktısı:

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

Diğer iş parçacığından daha büyük olan görevini bitirmeden önce 6 saniye sürdüğünü görebilirsiniz. Yani Future.get () görev tamamlanana kadar bekler.

Future.get () kullanmazsanız, bitmeyi beklemez ve zaman tüketimini esas alır.

Java eşzamanlılığı ile iyi şanslar.


Cevabınız için teşekkür ederim! Ben CountdownLatches kullandım ama seninki çok daha esnek bir yaklaşım.
Piskvor,

9

Basit bir örnek olmadan birçok doğru yanıt .. İşte kullanmanın kolay ve basit bir yolu 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();
        }
    }
}

Bu sınıfı şu şekilde kullanın, sonra:

ThreadEvent oluşturun:

ThreadEvent resultsReady = new ThreadEvent();

Yöntemde bu sonuçları bekliyor:

resultsReady.await();

Ve tüm sonuçlar oluşturulduktan sonra sonuçları oluşturan yöntemde:

resultsReady.signal();

DÜZENLE:

(Bu gönderiyi düzenlediğim için özür dilerim, ancak bu kodun yarış durumu çok kötü ve yorum yapacak kadar itibarım yok)

Bunu yalnızca sinyalin () await () 'den sonra çağrıldığından% 100 eminseniz kullanabilirsiniz. Windows Olayları gibi Java nesnesini kullanamamanızın büyük bir nedeni budur.

Kod şu sırayla çalışıyorsa:

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

sonra iplik 2 sonsuza kadar bekleyecektir . Bunun nedeni, Object.notify () işlevinin şu anda çalışan iş parçacıklarından yalnızca birini uyandırmasıdır. Daha sonra bekleyen bir iş parçacığı uyandırılmaz. Bu, olayların çalışmasını beklediğimden çok farklı, burada bir olay a) beklenene kadar veya b) açıkça sıfırlanana kadar sinyal verildi.

Not: Çoğu zaman notifyAll () kullanmalısınız, ancak bu yukarıdaki "sonsuza kadar bekle" sorunuyla ilgili değildir.


7

Düşük seviyeli şeylerin hepsinden çok daha az hataya eğilimli olan daha yüksek seviye senkronizasyon mekanizmaları sağlayan paketin dışında CountDownLatch sınıfını deneyin java.util.concurrent.


6

Bunu , iki iş parçacığı arasında paylaşılan bir Exchanger nesnesini kullanarak yapabilirsiniz :

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

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

Ve ikinci başlıkta:

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

}

Başkalarının dediği gibi, bu gönülsüz almayın ve sadece kodu kopyalayıp yapıştırın. Önce biraz okuma yapın.



2

Hızlı ve kirli bir şey istiyorsanız, while döngünüze bir Thread.sleep () çağrısı ekleyebilirsiniz. Veritabanı kitaplığı değiştiremeyeceğiniz bir şeyse, gerçekten başka kolay bir çözüm yoktur. Veritabanını bekleme süresiyle hazır olana kadar yoklamak performansı düşürmez.

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

Neredeyse zarif kod diyebileceğiniz bir şey değil, ancak işi hallediyor.

Veritabanı kodunu değiştirebilmeniz durumunda, diğer yanıtlarda önerildiği gibi bir muteks kullanmak daha iyidir.


3
Bu hemen hemen sadece beklemekle meşgul. Java 5'in util.concurrent paketlerindeki yapıları kullanmak doğru yol olmalıdır. stackoverflow.com/questions/289434/… bana şimdilik en iyi çözüm olarak görünüyor.
Cem Catikkas

Beklemekle meşgul, ancak yalnızca bu belirli yerde gerekliyse ve db kitaplığına erişim yoksa, başka ne yapabilirsiniz? Meşgul bekleme ille de kötü olmak zorunda değil
Mario Ortegón

2

Bu tüm diller için geçerlidir:

Bir olay / dinleyici modeline sahip olmak istiyorsunuz. Belirli bir olayı beklemek için bir dinleyici oluşturursunuz. Olay, çalışan iş parçacığınızda oluşturulur (veya bildirilir). Bu, şu anda sahip olduğunuz çözüm gibi bir koşulun karşılanıp karşılanmadığını görmek için sürekli yoklama yerine sinyal alınana kadar iş parçacığını bloke edecektir.

Durumunuz, kilitlenmelerin en yaygın nedenlerinden biridir - meydana gelen hatalardan bağımsız olarak diğer iş parçacığını işaretlediğinizden emin olun. Örnek - uygulamanız bir istisna atarsa ​​ve hiçbir zaman diğerine işlerin tamamlandığını bildirmek için yöntemi çağırmaz. Bu, diğer iş parçacığının asla 'uyanmaması' için onu yapacak.

Vakanızı uygulamadan önce bu paradigmayı daha iyi anlamak için olayları ve olay işleyicileri kullanma kavramlarına bakmanızı öneririm.

Alternatif olarak, iş parçacığının kaynağın serbest olmasını beklemesine neden olacak bir muteks kullanarak bir engelleme işlevi çağrısı kullanabilirsiniz. Bunu yapmak için iyi bir iş parçacığı senkronizasyonuna ihtiyacınız var - örneğin:

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

Bir iş parçacığındaki engelleme sırasından okuyabilir ve başka bir iş parçacığına yazabilirsiniz.


1

Dan beri

  1. join() dışlandı
  2. Zaten CountDownLatch kullanıyorsunuz ve
  3. Future.get () zaten diğer uzmanlar tarafından önerildi,

Diğer alternatifleri düşünebilirsiniz:

  1. invokeAll fromExecutorService

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

    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. ForkJoinPool veya newWorkStealingPool dan Executors(Java 8 çıktığından beri)

    Hedef paralellik düzeyi olarak tüm kullanılabilir işlemcileri kullanarak bir iş çalma iş parçacığı havuzu oluşturur.


-1

görüntü açıklamasını buraya girin

Bu fikir uygulanabilir mi? CountdownLatches veya Semaphores kullanıyorsanız mükemmel çalışıyor, ancak bir röportaj için en kolay cevabı arıyorsanız, bunun geçerli olabileceğini düşünüyorum.


1
Bu bir röportaj için nasıl olur ama gerçek kod için değil?
Piskvor,

Çünkü bu durumda birbiri ardına çalışıyor biri diğerini bekliyor. En iyi çözüm Semafor kullanmak olabilir, çünkü burada verilen en iyi cevap olan CountdownLatches kullanmak, Thread asla uykuya geçmez, bu da CPU döngülerinin kullanılması anlamına gelir.
Franco

Ancak mesele "onları sırayla çalıştırmak" değildi. Bunu daha net hale getirmek için soruyu düzenleyeceğim: GUI iş parçacığı veritabanı hazır olana kadar bekler ve ardından her ikisi de uygulamanın yürütülmesinin geri kalanı için aynı anda çalışır : GUI iş parçacığı DB iş parçacığına komutlar gönderir ve sonuçları okur. (Ve yine: Bir röportajda kullanılabilen ancak gerçek kodda kullanılamayan kodun amacı nedir? Tanıştığım çoğu teknik görüşmeci kod konusunda geçmişe sahipti ve aynı soruyu sorardı; artı gerçek bir uygulama için bu şeye ihtiyacım vardı O zamanlar yazıyordum, ödev yapmak için değil)
Piskvor

1
Yani. Bu, semafor kullanan bir üretici tüketici problemidir. Bir örnek yapmaya çalışacağım
Franco

Bir proje oluşturdum github.com/francoj22/SemProducerConsumer/blob/master/src/com/… . İyi çalışıyor.
Franco
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.