Anleitung zur Java TransferQueue

Anleitung zur Java TransferQueue

1. Überblick

In diesem Artikel betrachten wir das KonstruktTransferQueueaus dem Standardpaketjava.util.concurrent.

Einfach ausgedrückt, diese Warteschlange ermöglicht es uns, Programme gemäß dem Produzenten-Konsumenten-Muster zu erstellen und Nachrichten zu koordinieren, die von Produzenten an Konsumenten weitergeleitet werden.

Die Implementierung ähnelt tatsächlich denBlockingQueue, gibt uns jedoch die neue Möglichkeit, eine Form des Gegendrucks zu implementieren. Dies bedeutet, dass der Produzent blockiert bleibt, wenn der Produzent eine Nachricht mit dertransfer()-Methode an den Verbraucher sendet, bis die Nachricht verbraucht ist.

2. Ein Produzent - Null Verbraucher

Testen wir einetransfer()-Methode anhand derTransferQueue-Methode. Das erwartete Verhalten besteht darin, dass der Produzent blockiert wird, bis der Verbraucher die Nachricht aus der Warteschlange mit einertake()-Methode empfängt.

Um dies zu erreichen, erstellen wir ein Programm mit einem Produzenten, aber null Konsumenten. Der erste Aufruf vontransfer() vom Producer-Thread wird auf unbestimmte Zeit blockiert, da wir keine Konsumenten haben, die dieses Element aus der Warteschlange abrufen können.

Mal sehen, wie die KlasseProduceraussieht:

class Producer implements Runnable {
    private TransferQueue transferQueue;

    private String name;

    private Integer numberOfMessagesToProduce;

    public AtomicInteger numberOfProducedMessages
      = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 0; i < numberOfMessagesToProduce; i++) {
            try {
                boolean added
                  = transferQueue.tryTransfer("A" + i, 4000, TimeUnit.MILLISECONDS);
                if(added){
                    numberOfProducedMessages.incrementAndGet();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // standard constructors
}

Wir übergeben eine Instanz derTransferQueue zusammen mit einem Namen, den wir unserem Produzenten geben möchten, und der Anzahl der Elemente, die in die Warteschlange übertragen werden sollen, an den Konstruktor.

Beachten Sie, dass wir dietryTransfer()-Methode mit einem bestimmten Zeitlimit verwenden. Wir warten vier Sekunden, und wenn ein Produzent die Nachricht nicht innerhalb des angegebenen Zeitlimits übertragen kann, gibt erfalse zurück und fährt mit der nächsten Nachricht fort. Der Produzent hat einenumberOfProducedMessages-Variable, um zu verfolgen, wie viele Nachrichten produziert wurden.

Schauen wir uns als nächstes dieConsumer-Klasse an:

class Consumer implements Runnable {

    private TransferQueue transferQueue;

    private String name;

    private int numberOfMessagesToConsume;

    public AtomicInteger numberOfConsumedMessages
     = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 0; i < numberOfMessagesToConsume; i++) {
            try {
                String element = transferQueue.take();
                longProcessing(element);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void longProcessing(String element)
      throws InterruptedException {
        numberOfConsumedMessages.incrementAndGet();
        Thread.sleep(500);
    }

    // standard constructors
}

Es ähnelt dem Produzenten, aber wir empfangen Elemente aus der Warteschlange mit der Methodetake(). Wir simulieren auch eine Aktion mit langer Laufzeit, indem wir die MethodelongProcessing() verwenden, bei der wir die VariablenumberOfConsumedMessages inkrementieren, die ein Zähler der empfangenen Nachrichten ist.

Beginnen wir unser Programm jetzt mit nur einem Produzenten:

@Test
public void whenUseOneProducerAndNoConsumers_thenShouldFailWithTimeout()
  throws InterruptedException {
    // given
    TransferQueue transferQueue = new LinkedTransferQueue<>();
    ExecutorService exService = Executors.newFixedThreadPool(2);
    Producer producer = new Producer(transferQueue, "1", 3);

    // when
    exService.execute(producer);

    // then
    exService.awaitTermination(5000, TimeUnit.MILLISECONDS);
    exService.shutdown();

    assertEquals(producer.numberOfProducedMessages.intValue(), 0);
}

Wir möchten drei Elemente an die Warteschlange senden, aber der Produzent ist für das erste Element blockiert, und es gibt keinen Verbraucher, der dieses Element aus der Warteschlange. abruft. Wir verwenden die MethodetryTransfer()_ which will block until the message is consumed or the timeout is reached. After the timeout, it will return _false) s, um anzuzeigen, dass die Übertragung fehlgeschlagen ist, und es wird versucht, die nächste zu übertragen. Dies ist die Ausgabe aus dem vorherigen Beispiel:

Producer: 1 is waiting to transfer...
can not add an element due to the timeout
Producer: 1 is waiting to transfer...

3. Ein Produzent - ein Verbraucher

Testen wir eine Situation, in der es einen Produzenten und einen Konsumenten gibt:

@Test
public void whenUseOneConsumerAndOneProducer_thenShouldProcessAllMessages()
  throws InterruptedException {
    // given
    TransferQueue transferQueue = new LinkedTransferQueue<>();
    ExecutorService exService = Executors.newFixedThreadPool(2);
    Producer producer = new Producer(transferQueue, "1", 3);
    Consumer consumer = new Consumer(transferQueue, "1", 3);

    // when
    exService.execute(producer);
    exService.execute(consumer);

    // then
    exService.awaitTermination(5000, TimeUnit.MILLISECONDS);
    exService.shutdown();

    assertEquals(producer.numberOfProducedMessages.intValue(), 3);
    assertEquals(consumer.numberOfConsumedMessages.intValue(), 3);
}

DasTransferQueue wird als Austauschpunkt verwendet, und bis der Verbraucher ein Element aus der Warteschlange verbraucht, kann der Hersteller kein weiteres Element hinzufügen. Schauen wir uns die Programmausgabe an:

Producer: 1 is waiting to transfer...
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A0
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A0
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A1
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A1
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A2
Consumer: 1 received element: A2

Wir sehen, dass das Produzieren und Konsumieren von Elementen aus der Warteschlange aufgrund der Angabe vonTransferQueue. sequentiell ist

4. Viele Produzenten - viele Verbraucher

Im letzten Beispiel werden wir mehrere Verbraucher und mehrere Produzenten in Betracht ziehen:

@Test
public void whenMultipleConsumersAndProducers_thenProcessAllMessages()
  throws InterruptedException {
    // given
    TransferQueue transferQueue = new LinkedTransferQueue<>();
    ExecutorService exService = Executors.newFixedThreadPool(3);
    Producer producer1 = new Producer(transferQueue, "1", 3);
    Producer producer2 = new Producer(transferQueue, "2", 3);
    Consumer consumer1 = new Consumer(transferQueue, "1", 3);
    Consumer consumer2 = new Consumer(transferQueue, "2", 3);

    // when
    exService.execute(producer1);
    exService.execute(producer2);
    exService.execute(consumer1);
    exService.execute(consumer2);

    // then
    exService.awaitTermination(10_000, TimeUnit.MILLISECONDS);
    exService.shutdown();

    assertEquals(producer1.numberOfProducedMessages.intValue(), 3);
    assertEquals(producer2.numberOfProducedMessages.intValue(), 3);
}

In diesem Beispiel haben wir zwei Verbraucher und zwei Produzenten. Wenn das Programm startet, sehen wir, dass beide Produzenten ein Element produzieren können und danach blockieren sie, bis einer der Konsumenten dieses Element aus der Warteschlange nimmt:

Producer: 1 is waiting to transfer...
Consumer: 1 is waiting to take element...
Producer: 2 is waiting to transfer...
Producer: 1 transferred element: A0
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A0
Consumer: 1 is waiting to take element...
Producer: 2 transferred element: A0
Producer: 2 is waiting to transfer...
Consumer: 1 received element: A0
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A1
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A1
Consumer: 2 is waiting to take element...
Producer: 2 transferred element: A1
Producer: 2 is waiting to transfer...
Consumer: 2 received element: A1
Consumer: 2 is waiting to take element...
Producer: 1 transferred element: A2
Consumer: 2 received element: A2
Consumer: 2 is waiting to take element...
Producer: 2 transferred element: A2
Consumer: 2 received element: A2

5. Fazit

In diesem Artikel haben wir uns das KonstruktTransferQueueaus dem Paketjava.util.concurrentangesehen.

Wir haben gesehen, wie das Producer-Consumer-Programm mit diesem Konstrukt implementiert werden kann. Wir haben einetransfer()-Methode verwendet, um eine Form des Gegendrucks zu erzeugen, bei der ein Produzent kein anderes Element veröffentlichen kann, bis der Verbraucher ein Element aus der Warteschlange abruft.

TransferQueue kann sehr nützlich sein, wenn wir keinen überproduzierenden Produzenten wollen, der die Warteschlange mit Nachrichten überflutet, was zu Fehlern vonOutOfMemoryführt. Bei einem solchen Entwurf bestimmt der Verbraucher die Geschwindigkeit, mit der der Produzent Nachrichten produziert.

Alle diese Beispiele und Codefragmente finden Sie inover on GitHub - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.