2009-10-19 3 views
8

Questa domanda riguarda le raccolte Java, in particolare Hashtable e Vector, ma può essere applicata anche altrove.Programmazione di interfacce e raccolte sincronizzate

Ho letto in molti punti quanto sia bello programmare le interfacce e sono d'accordo al 100%. La possibilità di programmare un'interfaccia List, ad esempio, senza riguardo per l'implementazione sottostante è sicuramente utile per il disaccoppiamento e il test. Con le collezioni, posso vedere come una ArrayList e una LinkedList sono applicabili in diverse circostanze, date le differenze rispetto alla struttura di archiviazione interna, ai tempi di accesso casuale, ecc. Tuttavia, queste due implementazioni possono essere utilizzate con la stessa interfaccia ... che è grande.

Quello che non riesco a collocare è il modo in cui certe implementazioni sincronizzate (in particolare Hashtable e Vector) si adattano a queste interfacce. Per me, non sembrano adattarsi al modello. La maggior parte delle implementazioni della struttura dati sottostante sembrano variare nel modo in cui i dati vengono archiviati (LinkedList, Array, albero ordinato, ecc.), Mentre la sincronizzazione si occupa delle condizioni (condizioni di blocco) alle quali è possibile accedere ai dati. Diamo un'occhiata a un esempio in cui un metodo restituisce un insieme Mappa:

public Map<String, String> getSomeData(); 

Supponiamo che l'applicazione non è interessata a tutti con la concorrenza. In questo caso, operiamo su qualunque implementazione il metodo ritorni tramite l'interfaccia ... Tutti sono felici. Il mondo è stabile

Tuttavia, cosa succede se l'applicazione richiede ora attenzione sul fronte della concorrenza? Ora non possiamo operare senza riguardo per l'implementazione sottostante - Hashtable andrebbe bene, ma altre implementazioni devono essere soddisfatte. Consideriamo 3 scenari:

1) Applicare la sincronizzazione utilizzando i blocchi di sincronizzazione, ecc. Quando si aggiunge/rimuove con la raccolta. Questo non sarebbe tuttavia eccessivo nel caso in cui un'implementazione sincronizzata (Hashtable) venga restituita?

2) Modificare la firma del metodo per restituire Hashtable. Questo, tuttavia, ci vincola strettamente all'implementazione di Hashtable e, di conseguenza, i vantaggi della programmazione su un'interfaccia vengono lanciati fuori dalla finestra.

3) Utilizzare il pacchetto simultaneo e modificare la firma del metodo per restituire un'implementazione dell'interfaccia ConcurrentMap. Per me, questa sembra la via da seguire.

In sostanza, sembra proprio che alcune implementazioni sincronizzate siano un po 'inadeguate all'interno del framework delle collezioni in quanto, quando si programma alle interfacce, il problema della sincronizzazione quasi obbliga a pensare all'implementazione sottostante.

Mi manca completamente il punto qui?

Grazie.

+2

Penso che la chiave è che l'aggiunta di alcune parole chiave 'sincronizzate' qui o là non rende il programma sicuro per i thread. Anche con le raccolte 'java.util.concurrent', si desidera mantenerle come dettagli di implementazione privati. –

risposta

6

1) Sì, sarà eccessivo
2) Corretta, che non dovrebbe essere fatto
3) dipende dalla situazione.

Il fatto è che, come già sapete, la programmazione per l'interfaccia di descrivere che cosa fa l'applicazione (non come lo fa, questo è implementazione)

sincronizzazione è stato rimosso dalle implementazioni successive (ricordate, Vector e Hastable sono prima a java 1.2 in seguito arrivarono ArrayList e HasMap che non erano sincronizzati, ma tutti implementavano rispettivamente l'interfaccia List e Map, perché provocavano una penalizzazione delle prestazioni a causa dell'eccessiva sincronizzazione. Ad esempio, se si utilizza un vettore in un singolo thread, si ottiene comunque la sincronizzazione all'interno di quel singolo thread.

Condividere una struttura dati tra più thread è qualcosa che deve essere considerato quando si progetta l'applicazione. Lì sceglierai i metodi che utilizzerai e sceglierai chi è responsabile della pulizia dello stato dei dati.

Ecco dove scegli tra l'opzione 1 o 3 che hai menzionato. Ci sarebbe una sincronizzazione manuale? Dovremmo usare un'interfaccia sincronizzata? Quale versione sosterremo ecc ecc

Per esempio, se si sceglie 1, è possibile anche nel vostro disegno rifiutare alcune implementazioni (cioè vettore)

La sincronizzazione dei dati non è qualcosa che accade per "fortuna" davvero devi progettare affinché ciò avvenga correttamente e non causare più problemi a quelli che risolve.

Durante questa progettazione, è necessario prestare attenzione alle opzioni (le implementazioni) e/o all'infrastruttura sottostante che verranno utilizzate.

Il modo più semplice per evitare la sincronizzazione eccessiva consiste nell'utilizzare dati immutabili e non condividere i dati con altri thread.

Qualcosa di molto simile alla prima legge della distribuzione informatica da Martin Fowler:

"Quindi, abbiamo arrivare alla mia prima legge di Design oggetti distribuiti: Non distribuire gli oggetti."

Sarebbe la prima legge di applicazioni multithread essere:

Prima legge di applicazioni multithread: non condividere i tuoi dati?

:)

Nota finale: la classe Collections fornisce la versione "sincronizzata" di alcune interfacce:

Synchronized List
Synchronized Map
Synchronized Set

0

di Vector e Hashtable risalgono a pacchetto concorrenza attuale che è stata aggiunta in JDK 5. Al momento Vector è stato scritto, la gente pensava che fosse una buona idea per renderlo sincronizzato, allora probabilmente hanno colpito il muro prestazioni nell'uso enterprise Java. La concorrenza è certamente una di quelle situazioni in cui la modularità code-to-interface potrebbe non funzionare sempre.

+1

È un po 'più lungo che: 1.4 abbia introdotto un framework di collezioni aggiornato con java.util.Collections.synchronizedMap e gli amici che è stato consigliato di utilizzare invece di Hashtable. E poi 1.5 ha portato tutto il pacchetto concorrente. Ma Hashtable è ancora disseminato attorno alle librerie standard e probabilmente lo sarà sempre di più ... L'approccio "Java" sembra essere che la sincronizzazione è un dettaglio dell'implementazione, ei requisiti per esso dovrebbero essere comunicati attraverso la documentazione non tramite le dichiarazioni API . – araqnid

+0

Secondato. Entrambe le classi sono storiche e credo che risalgano a Java 1.0. Sono sicuro che risalgono alla 1.2, ma non vedono online le antiche API. –

+0

@araqnid, il modo di "approccio Java" è dipinto con sangue e cicatrici di prestazioni, concorrenza, ecc. Nel corso degli anni. Soprattutto da quando è decollato nell'uso lato server, la scala si è orientata verso l'iniezione di dipendenza, dal codice all'interfaccia, dall'astronautica all'architettura, rispetto al semplice "nuovo vettore();" –

1

Ciò che si sta affrontando è il fatto che in un ambiente a più thread, un client non può utilizzare in modo ingenuo un oggetto con stato mutabile e condiviso. L'interfaccia di raccolta, di per sé, non ti dice nulla su come l'oggetto può essere usato in modo sicuro. Restituire una ConcurrentMap aiuta a fornire alcune informazioni aggiuntive, ma solo per quel caso particolare.

Normalmente, è necessario comunicare i problemi di sicurezza filo separatamente nella documentazione (ad esempio, javadoc) oppure utilizzando le annotazioni personalizzate come descritto in Java Concurrency in Practice. Il client dell'oggetto reso deve utilizzare il proprio meccanismo di bloccaggio o uno che si fornire. L'interfaccia è solitamente ortogonale alla sicurezza del filo.

Non è un problema se il client sa che tutte le implementazioni provengono dalle implementazioni concorrenti, ma tali informazioni non sono comunicate dall'interfaccia stessa.