Създайте свой собствен ObjectPool в Java, част 1

Идеята за обединяване на обекти е подобна на работата на вашата локална библиотека: Когато искате да прочетете книга, знаете, че е по-евтино да заемете копие от библиотеката, вместо да закупите свое собствено копие. По същия начин е по-евтино (по отношение на паметта и скоростта) процесът да заема обект, вместо да създава свое собствено копие. С други думи, книгите в библиотеката представляват обекти, а покровителите на библиотеката - процесите. Когато процесът се нуждае от обект, той проверява копие от обектен пул, вместо да създава нов. След това процесът връща обекта в пула, когато вече не е необходим.

Има обаче няколко малки разлики между обединяването на обекти и аналогията на библиотеката, която трябва да се разбере. Ако покровителят на библиотеката иска конкретна книга, но всички копия на тази книга са изтеглени, покровителят трябва да изчака, докато копие бъде върнато. Никога не искаме процесът да трябва да чака обект, така че обектният пул ще създаде нови копия при необходимост. Това може да доведе до необикновено количество предмети, лежащи наоколо в басейна, така че ще води и преброяване на неизползваните обекти и ще ги почиства периодично.

Моят дизайн на обектен пул е достатъчно общ, за да се справя с времето за съхранение, проследяване и изтичане, но инстанцирането, валидирането и унищожаването на конкретни типове обекти трябва да се обработват чрез подкласиране.

Сега, когато основните неща са отстранени, нека влезем в кода. Това е скелетният обект:

 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 ){...} } 

Вътрешното хранилище на обединените обекти ще се обработва с два Hashtableобекта, единият за заключени обекти, а другият за отключен. Самите обекти ще бъдат ключовете на хеш-таблицата и тяхното време на последно използване (в епоха милисекунди) ще бъде стойността. Чрез съхраняване на последния път, когато е използван обект, пулът може да го изтече и да освободи памет след определена продължителност на неактивност.

В крайна сметка обектният пул ще позволи на подкласа да посочи първоначалния размер на хаштабите, заедно с техния темп на растеж и времето на изтичане, но се опитвам да го направя опростен за целите на тази статия, като кодирам твърдо тези стойности в конструктор.

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

Най checkOut()метод първите проверки, за да се види дали има някакви обекти в отключена HashTable. Ако е така, той преминава през тях и търси валиден. Проверката зависи от две неща. Първо, обектният пул проверява дали времето на последно използване на обекта не надвишава времето на изтичане, посочено от подкласа. Второ, обектният пул извиква абстрактния validate()метод, който извършва всяка специфична за класа проверка или реинициализация, необходима за повторно използване на обекта. Ако обектът не успее да провери, той се освобождава и цикълът продължава към следващия обект в хеш-таблицата. Когато се намери обект, който преминава проверка, той се премества в заключената хеш-таблица и се връща в процеса, който го е поискал. Ако отключената хеш-таблица е празна или нито един от нейните обекти не преминава проверка, нов обект се създава и се връща.

 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 ); } 

Това е най-сложният метод в ObjectPoolкласа, всичко е надолу от тук. В checkIn()метода просто премества премина в обект от заключена HashTable в отключена HashTable.

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

Трите останали метода са абстрактни и следователно трябва да бъдат приложени от подкласа. Заради тази статия ще създам пул за връзка с база данни, наречен JDBCConnectionPool. Ето скелета:

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

В JDBCConnectionPoolще се изисква приложението да уточни шофьор база данни, DSN, потребителско име, парола и при инстанция (чрез конструктора). (Ако всичко това е гръцко за вас, не се притеснявайте, JDBC е друга тема. Просто търпете с мен, докато се върнем към обединяването.)

 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; } 

Сега можем да се потопим в прилагането на абстрактните методи. Както видяхте в checkOut()метода, ObjectPoolще извика create () от неговия подклас, когато трябва да създаде екземпляр на нов обект. Защото JDBCConnectionPoolвсичко, което трябва да направим, е да създадем нов Connectionобект и да го предадем обратно. Отново, за да улесня тази статия, хвърлям внимание на вятъра и пренебрегвам всякакви изключения и нулеви указатели.

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

Преди да ObjectPoolосвободи обект с изтекъл срок (или невалиден) за събиране на боклука, той го предава на своя подкласиран expire()метод за всяко необходимо почистване в последния момент (много подобен на finalize()метода, извикан от събирача на боклука). В случая JDBCConnectionPoolвсичко, което трябва да направим, е да затворим връзката.

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

И накрая, трябва да приложим метода validate (), който ObjectPoolизвиква, за да се уверим, че обектът все още е валиден за употреба. Това е и мястото, където трябва да се извърши всякаква повторна инициализация. Защото JDBCConnectionPoolпросто проверяваме дали връзката все още е отворена.

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

Това е всичко за вътрешната функционалност. JDBCConnectionPoolще позволи на приложението да заема и връща връзки към база данни чрез тези невероятно прости и подходящо наречени методи.

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

Този дизайн има няколко недостатъка. Може би най-голямата е възможността за създаване на голям набор от обекти, които никога не се освобождават. Например, ако куп процеси едновременно поискат обект от пула, пулът ще създаде всички необходими екземпляри. Тогава, ако всички процеси върнат обектите обратно в пула, но checkOut()никога не бъдат извикани отново, нито един от обектите не се почиства. Това е рядко явление за активни приложения, но някои фонови процеси, които имат "неактивно" време, могат да създадат този сценарий. Реших този проблем с дизайна с нишка „почистване“, но ще запазя тази дискусия за втората половина на тази статия. Също така ще разгледам правилното боравене с грешки и разпространение на изключения, за да направя пула по-стабилен за критично важни приложения.

Томас Е. Дейвис е сертифициран Java програмист. В момента той живее в слънчева Южна Флорида, но страда като работохолик и прекарва по-голямата част от времето си на закрито.

Тази история „Създайте свой собствен ObjectPool в Java, част 1“ първоначално е публикувана от JavaWorld.