Programmering

Byg din egen ObjectPool i Java, del 1

Ideen om pooling af objekter svarer til driften af ​​dit lokale bibliotek: Når du vil læse en bog, ved du, at det er billigere at låne en kopi fra biblioteket i stedet for at købe din egen kopi. Ligeledes er det billigere (i forhold til hukommelse og hastighed) for en proces at låne et objekt i stedet for at oprette sin egen kopi. Med andre ord repræsenterer bøgerne i biblioteket objekter, og bibliotekets lånere repræsenterer processerne. Når en proces har brug for et objekt, tjekker den en kopi fra en objektpulje i stedet for at instantiere en ny. Processen returnerer derefter objektet til puljen, når det ikke længere er nødvendigt.

Der er dog et par mindre forskelle mellem objekt-pooling og biblioteksanalogen, der skal forstås. Hvis en bibliotekspatron ønsker en bestemt bog, men alle kopierne af den bog er tjekket ud, skal protektoren vente, indtil en kopi returneres. Vi ønsker ikke nogensinde, at en proces skal vente på et objekt, så objektpuljen vil instantiere nye kopier efter behov. Dette kan føre til en ublu mængde genstande, der ligger rundt i poolen, så det vil også holde en stemme på ubrugte genstande og rense dem med jævne mellemrum.

Mit objekt-pool-design er generisk nok til at håndtere opbevaring, sporing og udløbstider, men instantiering, validering og destruktion af bestemte objekttyper skal håndteres ved at underklasse.

Nu hvor det grundlæggende er ude af vejen, kan vi hoppe ind i koden. Dette er skeletobjektet:

 offentlig abstrakt klasse ObjectPool {privat lang udløbstid; privat Hashtable låst, ulåst; abstrakt Objekt skabe (); abstrakt boolsk validering (Objekt o); abstrakt tomrum udløber (Objekt o); synkroniseret objekt checkOut () {...} synkroniseret ugyldig checkIn (objekt o) {...}} 

Intern lagring af de samlede objekter håndteres med to Hashtable objekter, den ene til låste genstande og den anden til ulåste. Objekterne selv vil være nøglerne til hashtable, og deres sidste brugstid (i epoke millisekunder) vil være værdien. Ved at gemme sidste gang et objekt blev brugt, kan puljen udløbe det og frigøre hukommelse efter en bestemt varighed af inaktivitet.

I sidste ende vil objektpuljen tillade underklassen at specificere den oprindelige størrelse af hashtables sammen med deres vækstrate og udløbstiden, men jeg prøver at holde det simpelt i forbindelse med denne artikel ved hårdkodning af disse værdier i konstruktør.

 ObjectPool () {expirationTime = 30000; // 30 sekunder låst = ny Hashtable (); ulåst = ny Hashtable (); } 

Det checkOut () Metoden kontrollerer først, om der er objekter i den ulåste hashtable. I så fald cykler den igennem dem og ser efter en gyldig. Validering afhænger af to ting. For det første kontrollerer objektpuljen for at se, at objektets sidste brugstid ikke overstiger den udløbstid, der er specificeret af underklassen. For det andet kalder objektpuljen det abstrakte valider () metode, som udfører enhver klassespecifik kontrol eller geninitialisering, der er nødvendig for at genbruge objektet. Hvis objektet mislykkes med validering, frigøres det, og sløjfen fortsætter til det næste objekt i hashtabellen. Når der findes et objekt, der godkendes, flyttes det til den låste hashtabel og returneres til den proces, der anmodede om det. Hvis den ulåste hashtable er tom, eller ingen af ​​dens objekter godkendes, bliver et nyt objekt instantificeret og returneret.

 synkroniseret objekt checkOut () {lang nu = System.currentTimeMillis (); Objekt o; hvis (unlocked.size ()> 0) {Enumeration e = unlocked.keys (); mens (e.hasMoreElements ()) {o = e.nextElement (); hvis ((nu - ((Lang) ulåst.get (o)) .længVærdi ())> udløbstid) {// objekt er udløbet ulåst. fjern (o); udløbe (o); o = null; } ellers {if (validate (o)) {unlocked.remove (o); locked.put (o, ny Lang (nu)); returnere (o); } ellers {// objekt mislykkedes validering ulåst. fjern (o); udløbe (o); o = null; }}}} // ingen tilgængelige objekter, opret en ny o = create (); locked.put (o, ny Lang (nu)); returnere (o); } 

Det er den mest komplekse metode i ObjectPool klasse, det hele går nedad herfra. Det tjek ind () metode flytter simpelthen det indsendte objekt fra den låste hashtable til den ulåste hashtable.

synkroniseret ugyldig checkIn (Objekt o) {låst. fjern (o); unlocked.put (o, ny Long (System.currentTimeMillis ())); } 

De tre resterende metoder er abstrakte og skal derfor implementeres af underklassen. Af hensyn til denne artikel vil jeg oprette en databaseforbindelsespulje kaldet JDBCConnectionPool. Her er skeletet:

 offentlig klasse JDBCConnectionPool udvider ObjectPool {privat streng dsn, usr, pwd; public JDBCConnectionPool () {...} create () {...} validate () {...} expire () {...} public Connection loanConnection () {...} public void returnConnection () {. ..}} 

Det JDBCConnectionPool vil kræve, at applikationen angiver databasedriveren, DSN, brugernavn og adgangskode ved instantiering (via konstruktøren). (Hvis alt dette er græsk for dig, skal du ikke bekymre dig, JDBC er et andet emne. Bare vær med mig, indtil vi kommer tilbage til poolingen.)

 public JDBCConnectionPool (String driver, String dsn, String usr, String pwd) {prøv {Class.forName (driver) .newInstance (); } fange (Undtagelse e) {e.printStackTrace (); } this.dsn = dsn; this.usr = usr; this.pwd = pwd; } 

Nu kan vi dykke ned i implementeringen af ​​de abstrakte metoder. Som du så i checkOut () metode, ObjectPool vil kalde create () fra sin underklasse, når det er nødvendigt at instantiere et nyt objekt. Til JDBCConnectionPool, alt hvad vi skal gøre er at oprette et nyt Forbindelse modstand og send den tilbage. Igen for at holde denne artikel enkel kaster jeg forsigtighed mod vinden og ignorerer eventuelle undtagelser og null-pointer-forhold.

 Objekt skabe () {prøv {return (DriverManager.getConnection (dsn, usr, pwd)); } fange (SQLException e) {e.printStackTrace (); returnere (null); }} 

Før ObjectPool frigør et udløbet (eller ugyldigt) objekt til affaldsindsamling, det sender det til dets underklasse udløbe() metode til enhver nødvendig oprydning i sidste øjeblik (meget lig den færdiggør () metode kaldet af affaldssamleren). I tilfælde af JDBCConnectionPool, alt hvad vi skal gøre er at lukke forbindelsen.

ugyldigt udløbe (Objekt o) {prøv {((Forbindelse) o) .close (); } fange (SQLException e) {e.printStackTrace (); }} 

Og endelig er vi nødt til at implementere den validere () metode, der ObjectPool opkald for at sikre, at et objekt stadig er gyldigt til brug. Dette er også stedet, hvor enhver geninitialisering skal finde sted. Til JDBCConnectionPool, vi kontrollerer bare for at se, at forbindelsen stadig er åben.

 boolean validate (Object o) {try {return (! ((Connection) o) .isClosed ()); } fange (SQLException e) {e.printStackTrace (); returnere (falsk); }} 

Det er det for intern funktionalitet. JDBCConnectionPool vil give applikationen mulighed for at låne og returnere databaseforbindelser via disse utroligt enkle og passende navngivne metoder.

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

Dette design har et par fejl. Måske er den største muligheden for at oprette en stor pulje af objekter, der aldrig bliver frigivet. For eksempel, hvis en masse processer anmoder om et objekt fra puljen samtidigt, opretter puljen alle de nødvendige forekomster. Derefter, hvis alle processerne returnerer objekterne tilbage til puljen, men checkOut () bliver aldrig ringet op igen, ingen af ​​objekterne bliver ryddet op. Dette er en sjælden begivenhed for aktive applikationer, men nogle back-end-processer, der har "inaktiv" tid, kan give dette scenario. Jeg løste dette designproblem med en "oprydningstråd", men jeg gemmer den diskussion i anden halvdel af denne artikel. Jeg vil også dække korrekt håndtering af fejl og udbredelse af undtagelser for at gøre puljen mere robust til missionskritiske applikationer.

Thomas E. Davis er en Sun-certificeret Java-programmør. Han bor i øjeblikket i det solrige sydlige Florida, men lider som en workaholic og tilbringer det meste af sin tid indendørs.

Denne historie, "Byg din egen ObjectPool i Java, del 1" blev oprindeligt udgivet af JavaWorld.