11.10 Synchronisation der Datenstrukturen
 
Ein großer Unterschied zwischen den klassischen Datenstrukturen wie Vector oder Hashtable und denen aus der Collection-API besteht darin, dass alle Methoden durch synchronisierte Blöcke vor parallelen Änderungen geschützt waren. Bei den neuen Klassen wie ArrayList und HashMap sind Einfüge- und Löschoperationen nicht mehr automatisch synchronized. Sollen allerdings Listen, Mengen oder Assoziativspeicher gegen nebenläufige Änderungen sicher sein, gibt es zwei Möglichkeiten: Einmal über spezielle so genannte Lock-Free-(bzw. Wait-Free-)Algorithmen, die tatsächlich parallele Zugriffe – wenn möglich – erlauben, und einmal synchronisierende Wrapper.
11.10.1 Lock-Free-Algorithmen aus java.util.concurrent
 
Wenn zum Beispiel bei einer verketteten Liste ein Thread vorne etwas anhängt und der andere hinten etwas entfernt, ist das tatsächlich nebenläufig möglich, und es muss nicht die ganze Datenstruktur gelockt werden. Java 5 definiert über die Schnittstelle ConcurrentMap vier Operationen für Implementierungen, die atomar ausgeführt werden müssen:
|
V putIfAbsent( K key, V value ) |
|
boolean remove( Object key, Object value ) |
|
V replace( K key, V value ) und |
|
boolean replace( K key, V oldValue, V newValue ) |
Die bisher einzige Implementierung der Schnittstelle ist ConcurrentHashMap, eine sehr schnelle Datenstruktur für gleichzeitig operierende Threads. (Veränderungen durch den Iterator werden keine ConcurrentModificationException auslösen.)
Obwohl es keine Schnittstellen ConcurrentSet und ConcurrentList gibt, existiert zumindest eine Klasse ConcurrentLinkedQueue, die eine Thread-sichere und Lock-freie Liste (genauer: Queue) ist. Wie der Name schon andeutet, beruht die Realisierung auf der Basis von verketteten Listen und nicht von Arrays. Eine eigene ConcurrentSet könnte auf der Basis von ConcurrentHashMap implementiert werden, so wie auch HashSet intern mit einer HashMap realisiert ist.
11.10.2 Wrapper zur Synchronisation
 
Können die oben aufgeführten Datenstrukturen nicht verwendet werden, oder sind eigene Implementierungen gegeben, um nebenläufige Operationen abzusichern, beziehungsweise ein ganzes Bündel von Operationen mit einem existierenden Lock-Objekt, können Container extern synchronisiert werden. Dazu wird ein Wrapper mit einer statischen Funktion synchronizedXXX() angefordert. Die Wrapper arbeiten nicht Lock-Free, und Parallelität in den Datenstrukturen ist nicht gegeben.
Beispiel Eine synchronisierte Liste.
List<Byte> list = Collections.synchronizedList ( new LinkedList<Byte>() );
Die generischen Typen übernimmt die Funktion!
|
Diese Funktion liefert eine neue Sammlung, die sich wie eine Hülle um die existierende Datenstruktur legt und alle Funktionsaufrufe synchronisiert.
class java.util.Collections
|
|
static <T> Collection<T> synchronizedCollection( Collection<T> c ) |
|
static <T> List<T> synchronizedList( List<T> list ) |
|
static <K,V> Map<K,V> synchronizedMap( Map<K,V> m ) |
|
static <T> Set<T> synchronizedSet( Set<T> s ) |
|
static <K,V> SortedMap<K,V> synchronizedSortedMap( SortedMap<K,V> m ) |
|
static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s )
Liefert synchronisierte, also Thread-sichere Datenstrukturen. |
11.10.3 CopyOnWriteArrayList und CopyOnWriteArraySet
 
Ist die Anzahl der Lese-Operationen hoch, kann es sich lohnen, bei jedem Schreibzugriff erst die Daten zu kopieren und dann das Element hinzuzufügen, damit im Hintergrund andere Threads ohne einen Lock, der fürs Schreiben nötig ist, lesen können. Zwei dieser Datenstrukturen bietet Java an: CopyOnWriteArrayList für Listen und CopyOnWriteArraySet für Mengen. Die Klassen sind genau dann optimal, wenn wenig verändert – das ist teuer – und fast ausschließlich gelesen wird.
|