Ohjelmointi

Rakenna oma ObjectPool Java-sovellukseen, osa 1

Objektiyhdistelmän idea on samanlainen kuin paikallisen kirjaston toiminta: Kun haluat lukea kirjaa, tiedät, että on halvempaa lainata kopio kirjastosta sen sijaan, että ostat oman kopion. Samoin prosessin on halvempaa (suhteessa muistiin ja nopeuteen) lainata pikemminkin kuin luoda oma kopio. Toisin sanoen kirjaston kirjat edustavat esineitä ja kirjaston suojelijat edustavat prosesseja. Kun prosessi tarvitsee objektia, se tarkistaa kopion objektivarastosta pikemminkin kuin välittää uuden. Sitten prosessi palauttaa objektin pooliin, kun sitä ei enää tarvita.

Objektiyhdistelmän ja kirjaston analogian välillä on kuitenkin muutama pieni ero, joka tulisi ymmärtää. Jos kirjaston suojelija haluaa tietyn kirjan, mutta kaikki kirjan kopiot kirjataan ulos, suojelijan on odotettava, kunnes kappale palautetaan. Emme koskaan halua prosessin joutuvan odottamaan objektia, joten objektivarasto välittää tarvittaessa uusia kopioita. Tämä voi johtaa siihen, että uima-altaalla on kohtuuttoman suuri määrä esineitä, joten se pitää myös lukemat käyttämättömistä esineistä ja siivoaa ne säännöllisesti.

Objektivarastoni suunnittelu on riittävän yleinen käsittelemään varastointi-, seuranta- ja vanhentumisaikoja, mutta tiettyjen objektityyppien ilmentäminen, validointi ja tuhoaminen on hoidettava alaluokalla.

Nyt kun perusasiat poissa tieltä, antaa hypätä koodiin. Tämä on luuranko:

 julkinen abstrakti luokka ObjectPool {private long expirationTime; yksityinen Hashtable lukittu, lukitsematon; abstrakti Object create (); abstrakti looginen vahvistus (Object o); abstrakti mitätöinti umpeutuu (Object o); synkronoitu Object checkOut () {...} synkronoitu void checkIn (Object o) {...}} 

Yhdistettyjen esineiden sisäinen varastointi hoidetaan kahdella Hashtable esineitä, yksi lukittuja esineitä ja toinen lukitsematonta. Kohteet itse ovat hashtable-avaimet ja niiden viimeisen käyttöajan (aikakausina millisekunteina) arvo. Tallentamalla objektin viimeisen käyttökerran pooli voi vanhentua sen ja vapauttaa muistia määrätyn käyttämättömyyden keston jälkeen.

Viime kädessä objektijoukko antaisi alakategorialle mahdollisuuden määrittää hashtablejen alkuperäinen koko sekä niiden kasvunopeus ja vanhentumisaika, mutta yritän pitää sen yksinkertaisena tämän artikkelin tarkoituksiin koodaamalla nämä arvot kovasti rakentaja.

 ObjectPool () {expirationTime = 30000; // 30 sekuntia lukittu = uusi Hashtable (); lukitsematon = uusi Hashtable (); } 

Tarkista() method tarkistaa ensin, onko lukitsemattomassa hashtabelissa esineitä. Jos on, se selaa niitä ja etsii kelvollisen. Vahvistus riippuu kahdesta asiasta. Ensinnäkin objektivarasto tarkistaa, ettei objektin viimeinen käyttöaika ylitä alaluokan määrittelemää vanhentumisaikaa. Toiseksi objektivarasto kutsuu abstraktia vahvista () method, joka suorittaa minkä tahansa luokkakohtaisen tarkistuksen tai alustamisen, jota tarvitaan objektin uudelleenkäyttöön. Jos objektin vahvistus epäonnistuu, se vapautetaan ja silmukka jatkuu hashtable-objektin seuraavaan objektiin. Kun löydetään objekti, joka läpäisee tarkistuksen, se siirretään lukittuun hashtableen ja palautetaan sitä pyytäneeseen prosessiin. Jos lukitsematon hashtable on tyhjä tai mikään sen objekteista ei läpäise validointia, uusi objekti instantioidaan ja palautetaan.

 synkronoitu Object checkOut () {long now = System.currentTimeMillis (); Kohde o; if (unlocked.size ()> 0) {Enumerointi e = unlocked.keys (); while (e.hasMoreElements ()) {o = e.nextElement (); jos (((nyt - ((pitkä) lukitsematon.get (o)) .longValue ())> expirationTime) {// objekti on vanhentunut lukitsematon.poista (o); vanhentua (o); o = nolla; } else {if (validate (o)) {unlocked.remove (o); locked.put (o, uusi Long (nyt)); paluu (o); } else {// objektin epäonnistunut vahvistus avattu. poista (o); vanhentua (o); o = nolla; }}}} // esineitä ei ole käytettävissä, luo uusi o = create (); locked.put (o, uusi Long (nyt)); paluu (o); } 

Se on monimutkaisin menetelmä ObjectPool luokassa, kaikki on täältä alamäkeen. ilmoittautua() method yksinkertaisesti siirtää siirretyn objektin lukitusta hashtabesta lukitsemattomaan hashtableen.

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

Kolme muuta menetelmää ovat abstrakteja, ja siksi ne on pantava täytäntöön alaluokassa. Tämän artikkelin vuoksi aion luoda tietokantayhteyspoolin nimeltä JDBCConnectionPool. Tässä on luuranko:

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

JDBCConnectionPool vaatii sovelluksen määrittämään tietokannan ohjaimen, DSN: n, käyttäjänimen ja salasanan heti (rakennuttajan kautta). (Jos tämä on sinulle kaikki kreikkalaista, älä huoli, JDBC on toinen aihe. Pidä vain kanssani, kunnes palaamme yhdistämiseen.)

 public JDBCConnectionPool (String-ohjain, String dsn, String usr, String pwd) {kokeile {Class.forName (ohjain) .newInstance (); } catch (Poikkeus e) {e.printStackTrace (); } tämä.dsn = dsn; this.usr = usr; tämä.pwd = pwd; } 

Nyt voimme sukeltaa abstraktien menetelmien toteuttamiseen. Kuten näit Tarkista() menetelmä, ObjectPool kutsuu create () alaluokastaan, kun se tarvitsee instantisoida uuden objektin. Sillä JDBCConnectionPool, meidän on vain luotava uusi Yhteys ja anna se takaisin. Jälleen kerran, jotta tämä artikkeli olisi yksinkertainen, heitän varovaisuutta tuulelle ja jätän huomiotta kaikki poikkeukset ja nollaosoittimen ehdot.

 Objektin luonti () {try {return (DriverManager.getConnection (dsn, usr, pwd)); } catch (SQLException e) {e.printStackTrace (); paluu (null); }} 

Ennen ObjectPool vapauttaa vanhentuneen (tai virheellisen) objektin roskakoriin, se siirtää sen alaluokkaansa mennä umpeen() menetelmä tarvittavaan viime hetken puhdistukseen (hyvin samanlainen kuin viimeistellä () jätteen kerääjän kutsuma menetelmä). Siinä tapauksessa että JDBCConnectionPool, meidän tarvitsee vain sulkea yhteys.

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

Ja lopuksi meidän on toteutettava validate () -menetelmä ObjectPool kutsuu varmistaakseen, että objekti on edelleen kelvollinen käytettäväksi. Tämä on myös paikka, jossa kaikki uudelleenalustus tulisi tehdä. Sillä JDBCConnectionPool, vain tarkistamme, että yhteys on edelleen auki.

 looginen vahvistus (Object o) {try {return (! ((Connection) o) .isClosed ()); } catch (SQLException e) {e.printStackTrace (); paluu (väärä); }} 

Se on sisäinen toiminnallisuus. JDBCConnectionPool antaa sovelluksen lainata ja palauttaa tietokantayhteyksiä näiden uskomattoman yksinkertaisten ja osuvasti nimettyjen menetelmien avulla.

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

Tällä mallilla on pari puutetta. Ehkä suurin on mahdollisuus luoda suuri joukko esineitä, joita ei koskaan vapauteta. Esimerkiksi, jos joukko prosesseja pyytää objektia altaalta samanaikaisesti, pooli luo kaikki tarvittavat instanssit. Sitten, jos kaikki prosessit palauttavat objektit takaisin pooliin, mutta Tarkista() ei koskaan soiteta uudelleen, yksikään esineistä ei siivota. Tämä on harvinaista aktiivisten sovellusten kohdalla, mutta jotkut taustaprosessit, joilla on "tyhjäkäynti", saattavat tuottaa tämän skenaarion. Olen ratkaissut tämän suunnitteluongelman "puhdista" -säikeellä, mutta tallennan keskustelun tämän artikkelin toiselle puoliskolle. Käsittelen myös virheiden asianmukaista käsittelyä ja poikkeusten lisäämistä, jotta poolista tulee vankempi tehtäväkriittisiin sovelluksiin.

Thomas E. Davis on Sun-sertifioitu Java-ohjelmoija. Tällä hetkellä hän asuu aurinkoisessa Etelä-Floridassa, mutta kärsii työnarkolaisena ja viettää suurimman osan ajastaan ​​sisätiloissa.

Tämän tarinan "Build your own ObjectPool in Java, Part 1" julkaisi alun perin JavaWorld.