Erstellen Sie Ihren eigenen ObjectPool in Java, Teil 1

Die Idee des Objektpoolings ähnelt dem Betrieb Ihrer lokalen Bibliothek: Wenn Sie ein Buch lesen möchten, wissen Sie, dass es billiger ist, eine Kopie aus der Bibliothek auszuleihen, als eine eigene Kopie zu kaufen. Ebenso ist es für einen Prozess billiger (in Bezug auf Speicher und Geschwindigkeit), ein Objekt auszuleihen, als eine eigene Kopie zu erstellen. Mit anderen Worten, die Bücher in der Bibliothek repräsentieren Objekte und die Bibliotheksbenutzer repräsentieren die Prozesse. Wenn ein Prozess ein Objekt benötigt, checkt er eine Kopie aus einem Objektpool aus, anstatt eine neue zu instanziieren. Der Prozess gibt das Objekt dann an den Pool zurück, wenn es nicht mehr benötigt wird.

Es gibt jedoch einige geringfügige Unterschiede zwischen Objektpooling und der Bibliotheksanalogie, die verstanden werden sollten. Wenn ein Bibliotheksbenutzer ein bestimmtes Buch möchte, aber alle Kopien dieses Buches ausgecheckt sind, muss der Benutzer warten, bis eine Kopie zurückgegeben wird. Wir möchten nicht, dass ein Prozess auf ein Objekt warten muss, sodass der Objektpool bei Bedarf neue Kopien instanziiert. Dies kann dazu führen, dass eine unermessliche Menge von Objekten im Pool herumliegt, sodass auch nicht verwendete Objekte erfasst und regelmäßig gereinigt werden.

Mein Objektpooldesign ist generisch genug, um Speicher-, Nachverfolgungs- und Ablaufzeiten zu verarbeiten, aber die Instanziierung, Validierung und Zerstörung bestimmter Objekttypen muss durch Unterklassen erfolgen.

Nachdem die Grundlagen aus dem Weg geräumt sind, können wir in den Code springen. Dies ist das Skelettobjekt:

 public abstract class ObjectPool { private long expirationTime; private Hashtable locked, unlocked; abstract Object create(); abstract boolean validate( Object o ); abstract void expire( Object o ); synchronized Object checkOut(){...} synchronized void checkIn( Object o ){...} } 

Der interne Speicher der gepoolten Objekte wird mit zwei HashtableObjekten behandelt, eines für gesperrte Objekte und das andere für entsperrte Objekte. Die Objekte selbst sind die Schlüssel der Hashtabelle und ihre letzte Verwendungszeit (in Epochen-Millisekunden) ist der Wert. Durch Speichern der letzten Verwendung eines Objekts kann der Pool es ablaufen lassen und nach einer bestimmten Dauer der Inaktivität Speicher freigeben.

Letztendlich würde der Objektpool es der Unterklasse ermöglichen, die anfängliche Größe der Hashtabellen zusammen mit ihrer Wachstumsrate und der Ablaufzeit anzugeben, aber ich versuche, es für die Zwecke dieses Artikels einfach zu halten, indem ich diese Werte in der Konstrukteur.

 ObjectPool() { expirationTime = 30000; // 30 seconds locked = new Hashtable(); unlocked = new Hashtable(); } 

Die checkOut()Methode prüft zunächst, ob sich Objekte in der entsperrten Hashtabelle befinden. Wenn ja, durchläuft es sie und sucht nach einem gültigen. Die Validierung hängt von zwei Dingen ab. Zunächst überprüft der Objektpool, ob die letzte Verwendungszeit des Objekts die von der Unterklasse angegebene Ablaufzeit nicht überschreitet. Zweitens ruft der Objektpool die abstrakte validate()Methode auf, die alle klassenspezifischen Überprüfungen oder Neuinitialisierungen durchführt, die zur Wiederverwendung des Objekts erforderlich sind. Wenn das Objekt die Validierung nicht besteht, wird es freigegeben und die Schleife fährt mit dem nächsten Objekt in der Hashtabelle fort. Wenn ein Objekt gefunden wird, das die Validierung besteht, wird es in die gesperrte Hashtabelle verschoben und an den Prozess zurückgegeben, der es angefordert hat. Wenn die entsperrte Hashtabelle leer ist oder keines ihrer Objekte die Validierung besteht, wird ein neues Objekt instanziiert und zurückgegeben.

 synchronized Object checkOut() { long now = System.currentTimeMillis(); Object o; if( unlocked.size() > 0 ) { Enumeration e = unlocked.keys(); while( e.hasMoreElements() ) { o = e.nextElement(); if( ( now - ( ( Long ) unlocked.get( o ) ).longValue() ) > expirationTime ) { // object has expired unlocked.remove( o ); expire( o ); o = null; } else { if( validate( o ) ) { unlocked.remove( o ); locked.put( o, new Long( now ) ); return( o ); } else { // object failed validation unlocked.remove( o ); expire( o ); o = null; } } } } // no objects available, create a new one o = create(); locked.put( o, new Long( now ) ); return( o ); } 

Das ist die komplexeste Methode in der ObjectPoolKlasse, von hier aus geht es bergab. Die checkIn()Methode verschiebt das übergebene Objekt einfach von der gesperrten Hashtabelle in die entsperrte Hashtabelle.

synchronized void checkIn( Object o ) { locked.remove( o ); unlocked.put( o, new Long( System.currentTimeMillis() ) ); } 

Die drei verbleibenden Methoden sind abstrakt und müssen daher von der Unterklasse implementiert werden. Für diesen Artikel werde ich einen Datenbankverbindungspool namens erstellen JDBCConnectionPool. Hier ist das Skelett:

 public class JDBCConnectionPool extends ObjectPool { private String dsn, usr, pwd; public JDBCConnectionPool(){...} create(){...} validate(){...} expire(){...} public Connection borrowConnection(){...} public void returnConnection(){...} } 

Das JDBCConnectionPoolwird die Anwendung erfordert den Datenbanktreiber, DSN, Benutzername und Passwort bei der Instanziierung (über den Konstruktor) angeben. (Wenn Ihnen das alles griechisch ist, machen Sie sich keine Sorgen, JDBC ist ein anderes Thema.

 public JDBCConnectionPool( String driver, String dsn, String usr, String pwd ) { try { Class.forName( driver ).newInstance(); } catch( Exception e ) { e.printStackTrace(); } this.dsn = dsn; this.usr = usr; this.pwd = pwd; } 

Jetzt können wir uns mit der Implementierung der abstrakten Methoden befassen. Wie Sie in der checkOut()Methode gesehen haben, ObjectPoolruft create () aus seiner Unterklasse auf, wenn ein neues Objekt instanziiert werden muss. Für JDBCConnectionPoolalles, was wir tun müssen , ist ein neues erstellen ConnectionObjekt und wieder passieren. Um diesen Artikel einfach zu halten, warne ich den Wind und ignoriere alle Ausnahmen und Nullzeigerbedingungen.

 Object create() { try { return( DriverManager.getConnection( dsn, usr, pwd ) ); } catch( SQLException e ) { e.printStackTrace(); return( null ); } } 

Bevor ObjectPoolein abgelaufenes (oder ungültiges) Objekt für die Speicherbereinigung freigegeben wird, übergibt es es zur expire()erforderlichen Bereinigung in letzter Minute an seine untergeordnete Methode (sehr ähnlich der vom finalize()Speicherbereinigungsprogramm aufgerufenen Methode). Im Falle von JDBCConnectionPoolmüssen wir nur die Verbindung schließen.

void expire( Object o ) { try { ( ( Connection ) o ).close(); } catch( SQLException e ) { e.printStackTrace(); } } 

Und schließlich müssen wir die validate () -Methode implementieren, die ObjectPoolaufruft, um sicherzustellen, dass ein Objekt noch gültig ist. Dies ist auch der Ort, an dem eine Neuinitialisierung stattfinden sollte. Zum Beispiel JDBCConnectionPoolüberprüfen wir nur, ob die Verbindung noch offen ist.

 boolean validate( Object o ) { try { return( ! ( ( Connection ) o ).isClosed() ); } catch( SQLException e ) { e.printStackTrace(); return( false ); } } 

Das war's für die interne Funktionalität. JDBCConnectionPoolermöglicht es der Anwendung, Datenbankverbindungen über diese unglaublich einfachen und treffend benannten Methoden auszuleihen und zurückzugeben.

 public Connection borrowConnection() { return( ( Connection ) super.checkOut() ); } public void returnConnection( Connection c ) { super.checkIn( c ); } 

Dieser Entwurf hat ein paar Mängel. Das vielleicht größte ist die Möglichkeit, einen großen Pool von Objekten zu erstellen, der niemals freigegeben wird. Wenn beispielsweise mehrere Prozesse gleichzeitig ein Objekt aus dem Pool anfordern, erstellt der Pool alle erforderlichen Instanzen. Wenn dann alle Prozesse die Objekte in den Pool zurückgeben, aber checkOut()nie wieder aufgerufen werden, wird keines der Objekte bereinigt. Dies ist bei aktiven Anwendungen selten der Fall, aber einige Back-End-Prozesse mit "Leerlaufzeit" können dieses Szenario erzeugen. Ich habe dieses Designproblem mit einem "Bereinigungs" -Thread gelöst, aber ich werde diese Diskussion für die zweite Hälfte dieses Artikels speichern. Ich werde auch die ordnungsgemäße Behandlung von Fehlern und die Weitergabe von Ausnahmen behandeln, um den Pool für geschäftskritische Anwendungen robuster zu machen.

Thomas E. Davis ist ein Sun Certified Java Programmer. Derzeit lebt er im sonnigen Südflorida, leidet aber als Workaholic und verbringt die meiste Zeit in Innenräumen.

Diese Geschichte "Erstellen Sie Ihren eigenen ObjectPool in Java, Teil 1" wurde ursprünglich von JavaWorld veröffentlicht.