Quale implementazione di coda simultanea dovrei usare in Java?


132

Da JavaDocs:

  • Un ConcurrentLinkedQueue è una scelta appropriata quando molti thread condivideranno l'accesso a una raccolta comune. Questa coda non consente elementi null.
  • ArrayBlockingQueue è un classico "buffer limitato", in cui un array di dimensioni fisse contiene elementi inseriti dai produttori ed estratti dai consumatori. Questa classe supporta una politica di equità opzionale per l'ordinazione di thread di produttori e consumatori in attesa
  • LinkedBlockingQueue in genere ha un throughput più elevato rispetto alle code basate su array ma prestazioni meno prevedibili nella maggior parte delle applicazioni simultanee.

Ho 2 scenari, uno richiede la coda per supportare molti produttori (thread che lo utilizzano) con un consumatore e l'altro è il contrario.

Non capisco quale implementazione usare. Qualcuno può spiegare quali sono le differenze?

Inoltre, qual è la "politica di equità facoltativa" nel ArrayBlockingQueue?


1
Hai dimenticato di chiedere anche di PriorityBlockingQueue, utile per specificare un ordine in cui i thread vengono elaborati.
IgorGanapolsky,

Risposte:


53

Fondamentalmente la differenza tra loro sono le caratteristiche prestazionali e il comportamento di blocco.

Prendendo prima il più semplice, ArrayBlockingQueueè una coda di dimensioni fisse. Pertanto, se si imposta la dimensione su 10 e si tenta di inserire un 11 ° elemento, l'istruzione insert si bloccherà fino a quando un altro thread rimuoverà un elemento. Il problema della correttezza è ciò che accade se più thread tentano di inserire e rimuovere contemporaneamente (in altre parole durante il periodo in cui la coda è stata bloccata). Un algoritmo di equità assicura che il primo thread che chiede sia il primo thread che ottiene. Altrimenti, un determinato thread potrebbe attendere più a lungo di altri thread, causando comportamenti imprevedibili (a volte un thread impiegherà solo alcuni secondi perché altri thread avviati in seguito vengono elaborati per primi). Il compromesso è che ci vuole un sovraccarico per gestire l'equità, rallentando il rendimento.

La differenza più importante tra LinkedBlockingQueuee ConcurrentLinkedQueueè che se richiedi un elemento da LinkedBlockingQueueae la coda è vuota, il tuo thread aspetterà che ci sia qualcosa. UNConcurrentLinkedQueue tornerà immediatamente con il comportamento di una coda vuota.

Da quale dipende se hai bisogno del blocco. Dove hai molti produttori e un consumatore, sembra proprio così. D'altra parte, dove hai molti consumatori e un solo produttore, potresti non aver bisogno del comportamento di blocco e potresti essere felice di avere solo i consumatori che controllano se la coda è vuota e andare avanti se lo è.


67
La risposta è fuorviante. Sia LinkedBlockingQueue che ConcurrentLinkedQueue hanno il metodo "poll ()" che rimuove la testa della coda o restituisce null (non blocca) e il metodo "offer (E e)" che si inserisce nella coda della coda e non blocca. La differenza è che solo LinkedBlockingQueue ha operazioni di blocco oltre alle operazioni non bloccanti - e per quel privilegio, si paga il prezzo che LinkedBlockingQueue ha effettivamente un blocco. L'altra risposta spiega questo.
Nakedible,

123

ConcurrentLinkedQueue significa che non vengono eseguiti blocchi (ovvero nessuna chiamata sincronizzata (questa) o Lock.lock ). Utilizzerà un CAS: confronta e scambia durante le modifiche per vedere se il nodo head / tail è sempre lo stesso di quando è stato avviato. In tal caso, l'operazione ha esito positivo. Se il nodo head / tail è diverso, girerà e riproverà.

LinkedBlockingQueue prenderà un blocco prima di qualsiasi modifica. Quindi le chiamate della tua offerta verrebbero bloccate fino a quando non ottengono il blocco. È possibile utilizzare il sovraccarico dell'offerta che richiede un TimeUnit per dire che si è disposti ad attendere solo X tempo prima di abbandonare l'aggiunta (di solito è utile per le code di tipo messaggio in cui il messaggio è obsoleto dopo il numero X di millisecondi).

Equità significa che l'implementazione di Lock manterrà i thread ordinati. Ciò significa che se il thread A entra e quindi il thread B entra, il thread A otterrà prima il blocco. Senza equità, non è davvero definito ciò che accade. Molto probabilmente sarà il thread successivo che verrà programmato.

Per quanto riguarda quale da usare, dipende. Tendo a utilizzare ConcurrentLinkedQueue perché il tempo impiegato dai miei produttori per ottenere lavoro da mettere in coda è diverso. Non ho molti produttori che producono nello stesso preciso momento. Ma il lato del consumatore è più complicato perché il sondaggio non andrà in un piacevole stato di sonno. Devi gestirlo da solo.


1
E a quali condizioni ArrayBlockingQueue è meglio di LinkedBlockingQueue?
kolobok,

@akapelko ArrayBlockingQueue consente ordini più dettagliati.
IgorGanapolsky,

2
Che cosa significa - "girerà e riproverà". ?
Lester,

9

Il titolo della domanda menziona Blocking Queues. Tuttavia, nonConcurrentLinkedQueue è una coda di blocco.

I BlockingQueues sono ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, e SynchronousQueue.

Alcuni di questi non sono chiaramente in forma per il vostro scopo ( DelayQueue, PriorityBlockingQueue, e SynchronousQueue). LinkedBlockingQueuee LinkedBlockingDequesono identici, tranne per il fatto che quest'ultima è una coda a doppia estremità (implementa l'interfaccia Deque).

Dal momento che ArrayBlockingQueueè utile solo se si desidera limitare il numero di elementi, attenersi a LinkedBlockingQueue.


Ho rimosso la parola Blocking dal titolo, grazie. Fammi vedere se ho capito, cosa hai detto significa che LinkedBlockingQueue può essere utilizzato in più utenti / produce scenari sullo stesso oggetto?
David Hofmann,

1
Ho pensato che ArrayBlockingQueue consenta un ordinamento più dettagliato dei thread? Da qui il suo vantaggio.
IgorGanapolsky,

4

ArrayBlockingQueue ha un footprint di memoria inferiore, può riutilizzare il nodo dell'elemento, non come LinkedBlockingQueue che deve creare un oggetto LinkedBlockingQueue $ Node per ogni nuovo inserimento.


1
buon punto! preferisco ArrayBlockingQueue più di LinkedBlockingQueue
trilioni

2
Questo non è necessariamente vero - se la coda è quasi vuota per molto tempo ma deve essere in grado di crescere di dimensioni, ArrayBlockingQueueavrà un ingombro di memoria molto peggiore - ha comunque un ampio array allocato in memoria per tutto il tempo, mentre la LinkedBlockingQueueavrà un ingombro trascurabile memoria quando vicino a vuoto.
Krease

1
  1. SynchronousQueue(Tratto da un'altra domanda )

SynchronousQueueè più un handoff, mentre il LinkedBlockingQueuesolo consente un singolo elemento. La differenza è che la put()chiamata a SynchronousQueuenon tornerà finché non ci sarà una take()chiamata corrispondente , ma con una LinkedBlockingQueuedimensione 1, la put()chiamata (in una coda vuota) tornerà immediatamente. È essenzialmente l' BlockingQueueimplementazione per quando non vuoi davvero una coda (non vuoi mantenere alcun dato in sospeso).

  1. LinkedBlockingQueue( LinkedListImplementazione ma non esattamente JDK Implementazione di LinkedListesso utilizza il nodo di classe interno statico per mantenere i collegamenti tra gli elementi)

Costruttore per LinkedBlockingQueue

public LinkedBlockingQueue(int capacity) 
{
        if (capacity < = 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node< E >(null);   // Maintains a underlying linkedlist. ( Use when size is not known )
}

Classe di nodo Utilizzata per mantenere i collegamenti

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

3 ArrayBlockingQueue (implementazione dell'array)

Costruttore per ArrayBlockingQueue

public ArrayBlockingQueue(int capacity, boolean fair) 
{
            if (capacity < = 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity]; // Maintains a underlying array
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
}

IMHO La più grande differenza tra ArrayBlockingQueueed LinkedBlockingQueueè chiara dal costruttore, ha una struttura di dati sottostante Array e altri LinkedList .

ArrayBlockingQueueutilizza l' algoritmo a doppia condizione a singolo blocco ed LinkedBlockingQueueè una variante dell'algoritmo "due code di blocco" e ha 2 blocchi 2 condizioni (takeLock, putLock)


0

ConcurrentLinkedQueue è senza blocco, LinkedBlockingQueue non lo è. Ogni volta che invochi LinkedBlockingQueue.put () o LinkedBlockingQueue.take (), devi prima acquisire il blocco. In altre parole, LinkedBlockingQueue ha una scarsa concorrenza. Se ti interessano le prestazioni, prova ConcurrentLinkedQueue + LockSupport.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.