Programmering

Bygg din egen ObjectPool i Java, del 1

Ideen om pooling av objekter ligner på driften av det lokale biblioteket: Når du vil lese en bok, vet du at det er billigere å låne en kopi fra biblioteket i stedet for å kjøpe din egen kopi. På samme måte er det billigere (i forhold til minne og hastighet) for en prosess låne et objekt i stedet for å lage sin egen kopi. Med andre ord representerer bøkene i biblioteket objekter og bibliotekets lånere representerer prosessene. Når en prosess trenger et objekt, sjekker det ut en kopi fra et objektbasseng i stedet for å instansiere et nytt. Prosessen returnerer deretter objektet til bassenget når det ikke lenger er behov for det.

Det er imidlertid noen mindre forskjeller mellom objekt-pooling og biblioteksanalogien som skal forstås. Hvis en biblioteksbeskytter vil ha en bestemt bok, men alle kopiene av den boken blir sjekket ut, må beskytteren vente til en kopi returneres. Vi ønsker ikke noen gang at en prosess må vente på et objekt, så objektgruppen vil instantiere nye kopier etter behov. Dette kan føre til en ubehagelig mengde gjenstander som ligger rundt i bassenget, så det vil også holde en stemme på ubrukte gjenstander og rydde dem opp med jevne mellomrom.

Objektbassengdesignen min er generisk nok til å håndtere lagring, sporing og utløpstid, men instantiering, validering og ødeleggelse av bestemte objekttyper må håndteres ved å underklasse.

Nå som det grunnleggende er ute av veien, kan vi hoppe inn i koden. Dette er skjelettobjektet:

 offentlig abstrakt klasse ObjectPool {private long expirationTime; privat Hashtable låst, ulåst; abstrakt Objekt skape (); abstrakt boolsk validering (Objekt o); abstrakt tomrum utløper (Objekt o); synkronisert Object checkOut () {...} synkronisert ugyldig checkIn (Object o) {...}} 

Intern lagring av de samlede objektene vil bli håndtert med to Hashtable gjenstander, en for låste gjenstander og den andre for ulåste. Objektene i seg selv vil være nøklene til hashtabellen, og deres siste brukstid (i epoke millisekunder) vil være verdien. Ved å lagre sist et objekt ble brukt, kan bassenget utløpe det og frigjøre minne etter en spesifisert varighet av inaktivitet.

Til syvende og sist vil objektgruppen tillate underklassen å spesifisere den opprinnelige størrelsen på hashtables sammen med deres veksthastighet og utløpstid, men jeg prøver å holde det enkelt for formålet med denne artikkelen ved å hardkode disse verdiene konstruktør.

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

De Sjekk ut() metoden sjekker først for å se om det er noen objekter i den ulåste hashtabellen. I så fall sykler den gjennom dem og ser etter en gyldig. Validering avhenger av to ting. Først kontrollerer objektmassen for å se at objektets siste brukstid ikke overstiger utløpstiden spesifisert av underklassen. For det andre kaller objektbassenget abstrakt validere() metode, som utfører klassespesifikk kontroll eller reinitialisering som er nødvendig for å gjenbruke objektet. Hvis objektet mislykkes med validering, frigjøres det og sløyfen fortsetter til neste objekt i hashtabellen. Når et objekt blir funnet som godkjennes, flyttes det til den låste hashtabellen og returneres til prosessen som ba om det. Hvis den ulåste hashtabellen er tom, eller ingen av objektene deres godkjennes, blir et nytt objekt instantiert og returnert.

 synkronisert Object checkOut () {long now = System.currentTimeMillis (); Objekt o; hvis (unlocked.size ()> 0) {Enumeration e = unlocked.keys (); mens (e.hasMoreElements ()) {o = e.nextElement (); hvis ((nå - ((Lang) ulåst.get (o)) .longValue ())> utløpstid) {// objektet har utløpt ulåst. fjern (o); utløpe (o); o = null; } annet {if (validate (o)) {unlocked.remove (o); locked.put (o, ny Long (nå)); retur (o); } annet {// objekt mislyktes validering ulåst. fjern (o); utløpe (o); o = null; }}}} // ingen objekter tilgjengelig, opprett en ny o = create (); locked.put (o, ny Long (nå)); retur (o); } 

Det er den mest komplekse metoden i ObjectPool klasse, alt er utfor herfra. De innsjekking () metoden flytter ganske enkelt det innsendte objektet fra den låste hashtabellen til den ulåste hashtabellen.

synkronisert tomromsjekkIn (Object o) {locked.remove (o); ulåst.put (o, ny Long (System.currentTimeMillis ())); } 

De tre gjenværende metodene er abstrakte og må derfor implementeres av underklassen. Av hensyn til denne artikkelen skal jeg opprette en databaseforbindelsesbasseng kalt JDBCConnectionPool. Her er skjelettet:

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

De JDBCConnectionPool vil kreve at applikasjonen spesifiserer databasedriveren, DSN, brukernavn og passord ved instantiering (via konstruktøren). (Hvis alt dette er gresk for deg, ikke bekymre deg, JDBC er et annet emne. Bare vær med meg til vi kommer tilbake til bassenget.)

 offentlig JDBCConnectionPool (strengdriver, streng DSN, streng usr, streng pwd) {prøv {Class.forName (driver) .newInstance (); } fange (Unntak e) {e.printStackTrace (); } this.dsn = dsn; this.usr = usr; this.pwd = pwd; } 

Nå kan vi dykke ned i implementeringen av de abstrakte metodene. Som du så i Sjekk ut() metode, ObjectPool vil kalle create () fra underklassen når den trenger å sette i gang et nytt objekt. Til JDBCConnectionPool, alt vi trenger å gjøre er å lage et nytt Forbindelse objekt og gi den tilbake. Igjen, for å holde denne artikkelen enkel, kaster jeg forsiktighet mot vinden og ignorerer unntak og nullpekerforhold.

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

Før ObjectPool frigjør et utløpt (eller ugyldig) objekt for søppeloppsamling, det sender det til underklassen utløpe() metode for nødvendig opprydding i siste øyeblikk (ligner på fullføre () metode kalt av søppeloppsamleren). I tilfelle av JDBCConnectionPool, alt vi trenger å gjøre er å lukke forbindelsen.

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

Og til slutt må vi implementere validate () -metoden som ObjectPool ringer for å sikre at et objekt fremdeles er gyldig for bruk. Dette er også stedet der enhver ominitialisering skal finne sted. Til JDBCConnectionPool, vi sjekker bare for å se at forbindelsen fremdeles er åpen.

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

Det er det for intern funksjonalitet. JDBCConnectionPool vil tillate applikasjonen å låne og returnere databaseforbindelser via disse utrolig enkle og passende navngitte metodene.

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

Dette designet har et par feil. Kanskje den største er muligheten for å lage et stort utvalg av objekter som aldri blir frigitt. For eksempel, hvis en rekke prosesser ber om et objekt fra bassenget samtidig, vil bassenget opprette alle nødvendige forekomster. Så, hvis alle prosessene returnerer objektene tilbake til bassenget, men Sjekk ut() blir aldri kalt igjen, ingen av gjenstandene blir ryddet opp. Dette er en sjelden forekomst for aktive applikasjoner, men noen back-end-prosesser som har "inaktiv" tid, kan gi dette scenariet. Jeg løste dette designproblemet med en "ryddetråd", men jeg vil lagre den diskusjonen i andre halvdel av denne artikkelen. Jeg vil også dekke riktig håndtering av feil og spredning av unntak for å gjøre bassenget mer robust for oppdragskritiske applikasjoner.

Thomas E. Davis er en Sun-sertifisert Java-programmerer. For tiden bor han i det solfylte Sør-Florida, men lider som arbeidsnarkoman og tilbringer mesteparten av tiden sin innendørs.

Denne historien, "Build your own ObjectPool in Java, Part 1" ble opprinnelig utgitt av JavaWorld.

$config[zx-auto] not found$config[zx-overlay] not found