Über den Sinn und Unsinn von Pooling
In letzter Zeit tauchte bei meinen Trainings und Beratungen wieder das Thema Pooling auf.
Wir erinnern uns: Man schreibt das Jahr 1998. Alle Welt baut CORBA-Systeme, meistens mit C++. Thread-Sicherheit - gerade mit C++ - ist kompliziert. Neue Objekte zu bauen ist teuer. Also führt man jeden Request mit einem Thread in einem eigenen Objekt aus und um nicht andauernd neue Objekte zu bauen, poolt man sie - denn Objekterzeugung ist teuer. Die Implementierung dazu baut man manuell. Basierend darauf führt EJB dann dieses Modell in die Java-Welt ein, so dass man es einfach nutzen kann, statt es ständig erneut zu implementieren.
Mittlerweile schreiben wir das Jahr 2009. Verschiedene Dinge haben sich geändert:
- Pooling nimmt an, dass das Erzeugen und Zerstören von Objekten teuer ist - sonst könnte man sie ja jedesmal erzeugen und zerstören, statt sie zu poolen. Bei Objekten wie Datenbank-Verbindungen und Threads stimmt das auch nach wie vor. Aber Objekt-Erzeugung und Zerstörung einfacher Java-Objekte ist mittlerweile sehr billig. Kurzlebige Objekte sind praktisch umsonst. Die Hotspot-FAQ warnt sogar vor Pooling: http://java.sun.com/docs/hotspot/HotSpotFAQ.html#gc_pooling
Der Hintergrund ist die Speicherverwaltung von Java. Für ein neues Objekt holt Java sich bei den neueren VMs einfach nur einen neuen Speicherbereich in der Young Generation durch Erhöhen eines Pointers. Wenn die Young Generation voll ist, wird durch einen Copying Garbage Collector die überlebenden Objekte, auf die noch Referenzen existieren, umkopiert. Die Komplexität dieses Garbage Collectors hängt von der Anzahl überlebender Objetke ab. Kurzlebige Objekte bedeuten also nur, dass diese Garbage Collection öfter läuft, aber nicht, das sie länger dauert. Beim Pooling hingegen hat man langliebige Objekte, bei denen die Garbage Collection länger dauert, und durch das Ausgreifen aus dem Pool auch ein Overhead. Übrigens muss C++ bei der Objekt-Erzeugung den Heap nach einem passend großen Speicherplatz durchforsten. Das ist wesentlich komplexer als die Pointer-Manipulation bei Java. Ein Umkopieren der Objekte ist bei C++ nicht möglich und somit ist der Heap nach einiger Zeit fragmentiert, weil Objekte wieder freigegeben werden und so Lücken entstehen. Java und Garbage Collection können also gerade bei Speicherverwaltung effizienter sein als C++.
- Pooling geht ja davon aus, dass nur ein Thread zur Zeit ein Objekt nutzen kann. Aber die typischen Services, DAOs und Repositories heutiger Systeme sind von mehreren Threads parallel nutzbar. Sie sind zustandslos, so dass sie die wesentlichen Informationen über Parameter bekommen oder die Informationen hängen am Thread (zum Beispiel die aktuelle Transaktion oder die Datenbankverbindung). Nur in Ausnahmesituationen ist das Objekt nicht thread-safe - zum Beispiel wenn man statt eine lokalen Variable eine Instanz-Variable nutzt, die dann bei paralleler Ausführung der Methode in mehreren Threads überschrieben wird. Wenn man allerdings die Instanz-Variablen durch lokale ersetzt, hat man kein Problem - also ist das ein handwerklicher Programmierfehler.
Übrigens ist es interessant, dass kaum ein Spring-Entwickler weiß, wie man mit Spring Pooling aktiviert. Das bedeutet, dass es in der Praxis offensichtlich nicht benötigt wird - sonst würden es ja mehr Leute kennen.
Und noch ein Hinweis: EJB 3.1 definiert mittlerweile auch Singletons, wie sie bei Spring schon lange genutzt werden - weil Pooling eben oft unnötig ist. Aber die Annahme der Spezifikation ist offensichtlich, dass man sehr häufig nebenläufigen Zugriff auf die Singletons haben wird, die synchronisiert werden müssen. Wie bereits erwähnt, halt ich das wegen der typischen Zustandslosigkeit für die extreme Ausnahme. Diese Ausnahmen kann man sehr gut mit den Java-Mitteln wie dem
synchronized Schlüsselwort und den Klassen aus
java.util.concurrent zu Leibe rücken. Gerade diese Features heben ja Java von C++ ab. EJB 3.1 führt zusätzlich Annotationen ein, um diesem Problem zu begegnen. Warum die vorhandenen Mittel nicht reichen, ist zumindest mir unklar und ich glaube auch, dass man in den meisten Systemen meistens Singletons haben wird, die eh thread-safe sind. Und: Wenn schon ein neues Concurrency-Modell - warum dann nur für EJBs?
Einer der Gründe, warum SpringSource die Java-EE-6-Spezifikation nicht angenommen hat, sondern sich bei der Abstimmung enthalten hat, ist übrigens, dass dieser Ansatz für Concurrency mit Annotationen noch nie in der Praxis ausprobiert worden ist. Ein weiterer wichtiger Grund ist sicher, dass das ursprünglich geplante minimale Web-Profil nun in der Spezifikation doch nicht enthalten ist - obwohl genau dies die am meisten verwendete Plattform für Enterprise-Java-Entwicklung ist. Die vollständige Abstimmung inkl. unserem Kommentar findet sich unter
http://jcp.org/en/jsr/results?id=4821.
Labels: EJB, Pooling, Spring