Collections und Multithreading

Am Beispiel von Java soll hier etwas geschrieben werden, was viele Programmiersprachen betrifft, auch wenn die funktionalen Sprachen eine gewisse Immunität gegen derartige Probleme versprechen.

Es geht um Klassen, die sogenannte Collections enthalten. Nun kann man diese mit den sogenannten Gettern herausgeben lassen und vielelicht sogar mittels Settern austauschen oder ändern.

Eine naïve Implemntierung sieht etwa so aus:

import java.util.List;
import java.util.ArrayList;

public class C {
private final List l;

public C() {
this.l = new ArrayList();
}
public C(List l) {
this.l = l;
}

public List getL() {
return l;
}
....
}

Das sieht schön aus, ist aber recht gefährlich. Im Zusammenhang mit Multithreading kann es bsonders schwierig werden.

Das Problem dabei ist, daß der Konstruktor eine Liste übergeben bekommt. Diese wird nicht kopiert, sondern nur referenziert. Nun weiß man nicht genau, was der Progammteil, der den Konstruktor aufgerufen hat, sonst noch mit der Liste macht. So kann sich diese verändern, ohne daß bei C selbst irgendwelche Manipulationen gemacht wurden. Mit den Gettern wird die Liste wiederum an weitere Programmteile verfügbar gemacht, die alle darin etwas ändern können. Vielleicht sogar in mehreren Threads gleichzeitig, was sehr schnell zu merkwürdigen Exceptions führt, wenn man nicht speziell dafür ausgelegte Collections verwendet.

Wie kann man das lösen?

Oft hilft es, im Konstruktor die Liste zu kopieren und beim getter als immutable herauszugeben:

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class C {
private final List l;

public C() {
this.l = new ArrayList();
}
public C(List l) {
this.l = new ArrayList(l);
}

public List getL() {
return Collections.unmodifiableList(l);
}
....
}

Damit sind diese beiden Probleme abgestellt, zumindest in diesem Fall. Wenn es Listen von Listen oder andere kompliziertere Strukturen sind, dann treten die beschriebenen Probleme wegen der Unterlisten wieder auf. Hier sind die Elemente aber vom Typ String und damit selbst unveränderlich. Interessant ist nur die Frage, was passiert, wenn Methoden der Klasse C die darin gespeicherte Liste manipulieren. Die Programmteile, die vorher den getter aufgerufen haben und die dabei erhaltene Liste gespeichert haben, bekommen die Änderungen mit. Das kann erwünscht sein. Man kann es aber auch vermeiden, indem man beim getter noch einmal kopiert. Man kann sich das ewige kopieren auch sparen, indem man die Kopie „casht“ und wegwirft, sobald sich am Original etwas geändert hat.


import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class C {
private final List l;
private transient List ll = null;

public C() {
this.l = new ArrayList();
}
public C(List l) {
this.l = new ArrayList(l);
}

public synchronized List getL() {
if (ll == null) {
ll = Collections.unmodifiableList(new ArrayList(l));
}
return ll;
}

public synchronized void changeL() {
ll = null;
l.add("x" + System.currentTimeMillis());
}
}

Der Nachteil ist natürlich, daß dieses viele Kopieren Zeit kostet und dann auch noch den Garbage-Collector beschäftigt.

Wie sieht es nun in funktionalen Sprachen aus? Typischerweise sind dort solche Collections auch immutable, zumindest gibt es diese Variante. Jede Manipulation an der Collection läßt diese selbst unverändert und gibt eine Kopie zurück, die die Änderung enthält. Das ist natürlich auch auf den ersten Blick ineffizient, weil man zum Beispiel von einer Liste immer größere Kopien machen muß, um nur jeweils ein Element hinzuzufügen. Das wird aber nicht so heiß gegessen wie gekocht. Man kann eine Liste implementieren, die sich so verhält, als würde man jedes Mal eine Kopie anlegen. In Wirklichkeit kann man intern einige Optimierungen vornehmen, wenn zum Beispiel ein Zwischenzustand dieser Operationen nie weitervewendet wird. So zeigt es sich, daß funktionale Programmiersprachen trotz der scheinbar ineffizienten Kopiererei sehr effizient sein können.

Share Button

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.


*